npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bini-env

v1.1.1

Published

Universal environment variable loader and Vite plugin for Bini.js — powered by Hono, works across Node.js, Bun, Deno, Vercel Edge, Netlify Edge, and Cloudflare Workers

Readme

bini-env

npm npm downloads license vite hono typescript node

Zero-config environment variable system + Vite plugin for Bini.js

Powered by Hono — reads env vars from the Hono request context so variables are always resolved from the correct runtime binding, whether you're on Node.js, Bun, Deno, Vercel Edge, Netlify Edge, or Cloudflare Workers.


Features

  • Hono-nativegetEnv(c, key) / requireEnv(c, key) read directly from the Hono request context
  • Universal runtime — CF Workers, Node, Bun, Deno, Vercel Edge, Netlify Edge — Hono's adapter handles all of it
  • Zero dotenv — no .env parsing at runtime; vars come from the host platform
  • Vite-native dev.env loading, file watching, and server restarts are handled by Vite itself
  • Dev banner — prints ß Bini.js (dev/preview) and lists detected .env files on server start
  • Prefix controlBINI_ and VITE_ included out of the box, extend with your own
  • Tree-shakeable — nothing lands in the client bundle that shouldn't
  • Typed — full TypeScript support with an exported HonoContext type

Installation

pnpm add bini-env hono
# or
npm install bini-env hono
# or
yarn add bini-env hono

hono is a required peer dependency.


Quick Start

1. Register the Vite plugin

// vite.config.ts
import { defineConfig } from 'vite'
import { biniEnv } from 'bini-env'

export default defineConfig({
  plugins: [biniEnv()]
})

2. Read env vars in your Hono handlers

// src/app/api/hello.ts
import { Hono } from 'hono'
import { getEnv, requireEnv } from 'bini-env'

const app = new Hono()

app.post('/hello', async (c) => {
  try {
    const ctx = c as any

    // requireEnv throws if the var is missing — fail fast on required config
    const apiKey  = requireEnv(ctx, 'MY_API_KEY')

    // getEnv returns undefined if missing — use ?? to provide a default
    const appName = getEnv(ctx, 'APP_NAME')     ?? 'World'
    const timeout = parseInt(getEnv(ctx, 'TIMEOUT_MS') ?? '5000')

    return c.json({ message: `Hello, ${appName}!` })

  } catch (error: any) {
    if (error.message?.includes('[bini-env] Missing required')) {
      return c.json({ error: error.message }, 500)
    }
    return c.json({ error: 'Something went wrong.' }, 500)
  }
})

export default app

Usage Pattern

Always pass c explicitly. Cast it once at the top of the handler, then use ctx throughout.

app.post('/example', async (c) => {
  try {
    const ctx = c as any

    // Required vars — handler throws immediately if missing
    const dbUrl  = requireEnv(ctx, 'DATABASE_URL')
    const apiKey = requireEnv(ctx, 'STRIPE_SECRET_KEY')

    // Optional vars — fall back to sensible defaults
    const model      = getEnv(ctx, 'AI_MODEL')    ?? 'gpt-4o'
    const region     = getEnv(ctx, 'AWS_REGION')  ?? 'us-east-1'
    const maxRetries = parseInt(getEnv(ctx, 'MAX_RETRIES') ?? '3')
    const debug      = getEnv(ctx, 'DEBUG_MODE')  === 'true'

    // ... rest of handler

  } catch (error: any) {
    if (error.message?.includes('[bini-env] Missing required')) {
      return c.json({ error: error.message }, 500)
    }
    return c.json({ error: 'Something went wrong.' }, 500)
  }
})

The pattern in three steps:

  1. const ctx = c as any — once, at the top of the handler
  2. requireEnv(ctx, 'KEY') — for vars the handler cannot run without
  3. getEnv(ctx, 'KEY') ?? 'default' — for optional vars with sensible defaults

Environment Prefixes

BINI_ — server-side vars

BINI_ is bini-env's own prefix. Variables are not exposed to import.meta.env by default, making it the right choice for server-side config and secrets.

To additionally expose a BINI_ var to the client, add it to envPrefix explicitly:

biniEnv({ envPrefix: ['BINI_', 'VITE_', 'MY_PUBLIC_'] })

VITE_ — public client vars

VITE_ is Vite's built-in prefix. Any var starting with VITE_ is bundled into your client-side JavaScript and is publicly visible.

VITE_ANALYTICS_ID=UA-XXXX             # bundled into client JS — public
VITE_API_URL=https://api.example.com  # bundled into client JS — public
// accessible anywhere, including the browser
import.meta.env.VITE_ANALYTICS_ID

Choosing the right prefix

# ✓ use VITE_ for anything the browser needs
VITE_API_URL=https://api.example.com

# ✓ use BINI_ for server-side config
BINI_FEATURE_FLAG=true

# ✓ no prefix for secrets — read via getEnv(ctx, key) only
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_...

Avoid defining the same variable under two different prefixes — your code will silently read whichever one it finds first.

Prefix summary

| Prefix | Exposed to browser | Use for | |--------|--------------------|---------| | BINI_ | No (unless added to envPrefix) | Server-side config | | VITE_ | Yes, always | Public client config | | No prefix | No | Secrets — server only |


Platform Support

getEnv and requireEnv delegate to Hono's env(c) adapter, which reads from the correct source on every supported platform automatically. Your code never changes regardless of where it deploys.

| Platform | Runtime | Env source | How Hono reads it | |----------|---------|------------|-------------------| | Node.js | Node | System env / platform dashboard | process.env ✅ | | Bun | Bun | System env / platform dashboard | process.env ✅ | | Vercel Edge | V8 isolate | Project settings → Env Vars | process.env ✅ | | Netlify Edge | Deno | Site settings → Env Vars | Deno.env.get() ✅ | | Cloudflare Workers | V8 isolate | wrangler.toml / dashboard | CF bindings via c.env ✅ | | Deno Deploy | Deno | Project settings → Env Vars | Deno.env.get() ✅ |

Cloudflare note: Secrets set via wrangler secret put are only available inside the fetch handler via c.env — this is a Workers architecture constraint. getEnv(ctx, key) reads them correctly as long as you pass c.


How It Works

The plugin — biniEnv()

Does two things: tells Vite which env prefixes to expose to import.meta.env, and prints the ß Bini.js banner with detected .env files when the dev or preview server starts.

// what biniEnv() does internally
config() {
  return { envPrefix: ['BINI_', 'VITE_', ...yourExtras] }
}

On server start you will see:

  ß Bini.js (dev)
  ➜  Environments: .env.local, .env
  ➜  Local:   http://localhost:3000/
  ➜  Network: http://192.168.1.7:3000/

Vite handles everything else natively: loading .env, .env.local, .env.[mode] files during dev, watching and restarting on change, injecting prefixed vars into import.meta.env at build time, and HMR when env files change. bini-env does not reimplement any of that.

The env functions — getEnv / requireEnv

Both functions read exclusively from env(c) via hono/adapter — the Hono request context. There are no process.env fallbacks. Every read is request-scoped and resolved by Hono's adapter for the current platform.

dotenv is never used. In production, vars are set in your hosting platform's environment config.


API Reference

getEnv(c, key)

Returns string | undefined. Reads from the Hono request context.

app.get('/config', async (c) => {
  const ctx = c as any

  const region   = getEnv(ctx, 'AWS_REGION') ?? 'us-east-1'
  const logLevel = getEnv(ctx, 'LOG_LEVEL')  ?? 'info'
  const debug    = getEnv(ctx, 'DEBUG_MODE') === 'true'

  return c.json({ region, logLevel, debug })
})

requireEnv(c, key)

Returns string. Throws immediately if the variable is missing or empty. Logs a descriptive error to the terminal on failure.

app.post('/send-email', async (c) => {
  try {
    const ctx = c as any

    const smtpHost = requireEnv(ctx, 'SMTP_HOST')
    const smtpPass = requireEnv(ctx, 'SMTP_PASS')
    const smtpPort = parseInt(getEnv(ctx, 'SMTP_PORT') ?? '587')

    // ... send email

    return c.json({ sent: true })

  } catch (error: any) {
    if (error.message?.includes('[bini-env] Missing required')) {
      return c.json({ error: error.message }, 500)
    }
    return c.json({ error: 'Failed to send email.' }, 500)
  }
})

On failure, the terminal will show:

[bini-env] error  Missing required environment variable: "SMTP_HOST"
  -> Set it in your platform's env config or hosting dashboard.

biniEnv(options?)

Vite plugin. Registers BINI_ and VITE_ as env prefixes and optionally adds more.

biniEnv()
// or with extra prefixes
biniEnv({ envPrefix: ['MY_PUBLIC_'] })

| Option | Type | Default | Description | |--------|------|---------|-------------| | envPrefix | string \| string[] | [] | Extra prefixes to expose to import.meta.env, in addition to BINI_ and VITE_ |

biniLogger

Vite-style terminal logger. Use it in your own Bini.js plugins or server-side code.

import { biniLogger } from 'bini-env'

biniLogger.info('Server ready')
biniLogger.warn('Missing optional var')
biniLogger.error('Something broke', error)

HonoContext

Exported type (Context from Hono). Use it to type helper functions that group env reads, so you only cast c as any once per entry point.

import type { HonoContext } from 'bini-env'

function readDbConfig(c: HonoContext) {
  const ctx = c as any
  return {
    url:      requireEnv(ctx, 'DATABASE_URL'),
    poolSize: parseInt(getEnv(ctx, 'DB_POOL_SIZE') ?? '10'),
    ssl:      getEnv(ctx, 'DB_SSL') !== 'false',
  }
}

Performance

| Metric | Dev | Prod | |--------|-----|------| | File reads | 0 | 0 | | Runtime cost | ~0ms | 0 | | Bundle impact | Minimal | Tree-shaken |

No dotenv. No disk reads. No caching layer. getEnv is a direct call to Hono's adapter on every invocation — request-scoped and correct.


Troubleshooting

"Env var is undefined in production"

Vars set in .env files are only loaded by Vite during local development. In production, set your variables in your hosting platform's environment dashboard (Vercel, Netlify, Cloudflare, etc.).

"Works in dev, undefined in prod"

Same as above. Local dev works because Vite loads .env files automatically. Production requires platform-level configuration.

"Cloudflare secret not found"

Secrets set via wrangler secret put are only available via c.env inside a handler — which is exactly what getEnv(ctx, key) reads. Ensure you are passing c to the function.

"TypeScript error: Context not assignable to HonoContext"

Hono 4.12+ added a symbol to HonoRequest that can break structural assignability in strict TypeScript projects. Cast once per handler:

const ctx = c as any

"Types not found"

Add to your tsconfig.json or entry file:

/// <reference types="vite/client" />

Compatibility

| Tool | Version | Notes | |------|---------|-------| | Vite | 8.x | envPrefix hook is fully compatible; empty prefix array is never passed | | Hono | 4.x | hono/adapter env() is stable across all 4.x releases | | TypeScript | 5.x | Full type support via exported HonoContext | | Node.js | ≥ 20.19 | Minimum required version |


Contributing

PRs are welcome. The goal of this library is to stay minimal and runtime-cost-free. Contributions that add unnecessary complexity, increase bundle size, or introduce startup overhead will not be accepted.


License

MIT © Bini.js Team