bini-env
OfficialEnvironment variable system + Vite plugin for Bini.js. Hono-native, universal runtime, zero-config dev secrets.
Overview
bini-env reads environment variables from the Hono request context, so they always resolve from the correct runtime binding - Node.js, Bun, Deno, Vercel Edge, Netlify Edge, or Cloudflare Workers - without any per-platform code.
Installation
$ npm install bini-env hono
hono is a required peer dependency.Quick Start
1. Register the plugin
import { defineConfig } from 'vite'
import { biniEnv } from 'bini-env'
export default defineConfig({
plugins: [biniEnv()],
})2. Read env vars in a Hono handler
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
// Throws if missing — use for required config
const apiKey = requireEnv(ctx, 'MY_API_KEY')
// Returns undefined if missing — use for optional config
const appName = getEnv(ctx, 'APP_NAME') ?? 'World'
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 app3. Your .env file
# Server-side only
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_...
# Exposed to the browser
BINI_API_URL=https://api.example.com
VITE_ANALYTICS_ID=UA-XXXX4. Dev banner
Bini.js (dev) Environments: .env.local, .env Local: http://localhost:3000/ Network: http://192.168.1.7:3000/
Prefixes
Two prefixes are exposed to the browser: BINI_ and VITE_. Everything else stays server-side.
| Prefix | Browser | Server-side | Use for |
|---|---|---|---|
| No prefix | Never | Yes | Secrets - API keys, DB URLs, tokens |
| BINI_ | Always | Yes | Public config |
| VITE_ | Always | Yes | Public config |
Platform Support
getEnv / requireEnv delegate to Hono's env(c) adapter, which reads from the correct source on every platform automatically.
| Platform | Runtime | Source |
|---|---|---|
| Node.js | Node | process.env |
| Bun | Bun | process.env |
| Vercel Edge | V8 isolate | process.env |
| Netlify Edge | Deno | Deno.env.get() |
| Cloudflare Workers | V8 isolate | c.env |
| Deno Deploy | Deno | Deno.env.get() |
wrangler secret put are only available via c.env inside a handler. Pass c to getEnv / requireEnv and they resolve correctly.API Reference
getEnv(c, key)
Returns string | undefined. Use for optional config.
const region = getEnv(ctx, 'AWS_REGION') ?? 'us-east-1'
const debug = getEnv(ctx, 'DEBUG_MODE') === 'true'requireEnv(c, key)
Returns string. Throws if the variable is missing or empty.
[bini-env] error Missing required environment variable: "SMTP_HOST" -> Set it in your platform's env config or hosting dashboard.
Troubleshooting
- Works in dev, undefined in prod -
.envfiles are only loaded by Vite during dev and preview. In production, set vars in your hosting platform's dashboard. - My .env value isn't taking effect in dev - either the value is already set in your shell / CI (which wins), or it's empty (
KEY=), which is skipped by design. - requireEnv throws even though the key is in .env - an empty
KEY=is treated as unset. Give it a real value, or remove the line. - bini-env reads .env from the wrong folder - it uses Vite's
envDir, thenroot, then the working directory. CheckenvDir/rootinvite.config.tsif your.envlives somewhere non-standard. - Cloudflare secret not found -
wrangler secret putsecrets only live onc.env. PassctogetEnv/requireEnv. - TypeScript: Context not assignable to HonoContext - Hono 4.12+ added a symbol to
HonoRequestthat breaks strict assignability. Cast once per handler:const ctx = c as any.
Compatibility
| Tool | Version |
|---|---|
| Vite | 8.x |
| Hono | 4.x |
| TypeScript | 5.x |
| Node.js | ≥ 20.19 |