Using Environment Variables in API Routes
Read environment variables in API routes with getEnv and requireEnv.
Overview
In API routes, environment variables are read with getEnv(c, key) and requireEnv(c, key). Both read from the Hono request context via hono/adapter - that is what makes them work on every runtime.
getEnv and requireEnv are auto-imported in API routes - you do not need to write the import from bini-env manually.const ctx = c as any, then use ctx throughout. No process.env fallbacks - every read is request-scoped.Basic Usage
// src/app/api/hello.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (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 appRequired vs Optional
Use requireEnv for variables your app cannot run without. Use getEnv with ?? for optional configuration.
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'
return c.json({ model, region, maxRetries, debug })
} 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)
}
})| Function | Use for | Behavior |
|---|---|---|
| requireEnv(ctx, key) | Required config - app cannot run without | Throws if missing or empty |
| getEnv(ctx, key) ?? default | Optional config - fallback to default | Returns undefined if missing |
Complete Example
A full API endpoint that uses environment variables for configuration:
// src/app/api/email.ts
import { Hono } from 'hono'
import nodemailer from 'nodemailer'
const app = new Hono()
app.post('/email/send', async (c) => {
try {
const ctx = c as any
const smtpHost = requireEnv(ctx, 'SMTP_HOST')
const smtpUser = requireEnv(ctx, 'SMTP_USER')
const smtpPass = requireEnv(ctx, 'SMTP_PASS')
const fromEmail = requireEnv(ctx, 'FROM_EMAIL')
const smtpPort = parseInt(getEnv(ctx, 'SMTP_PORT') ?? '587')
const secure = getEnv(ctx, 'SMTP_SECURE') === 'true'
const debug = getEnv(ctx, 'DEBUG_MODE') === 'true'
const appName = getEnv(ctx, 'APP_NAME') ?? 'Bini.js App'
const transporter = nodemailer.createTransport({
host: smtpHost,
port: smtpPort,
secure,
auth: { user: smtpUser, pass: smtpPass },
debug,
})
const { to, subject, text } = await c.req.json()
if (!to || !subject || !text) {
return c.json({ error: 'Missing required fields: to, subject, text' }, 400)
}
await transporter.sendMail({
from: fromEmail,
to,
subject: `[${appName}] ${subject}`,
text,
})
return c.json({
success: true,
message: 'Email sent',
from: fromEmail,
app: appName,
})
} catch (error: any) {
if (error.message?.includes('[bini-env] Missing required')) {
return c.json({ error: error.message }, 500)
}
console.error('Email error:', error)
return c.json({ error: 'Failed to send email.' }, 500)
}
})
export default appError Handling
Always handle errors from requireEnv gracefully:
app.get('/config', async (c) => {
try {
const ctx = c as any
const apiKey = requireEnv(ctx, 'API_KEY')
const secret = requireEnv(ctx, 'SECRET_TOKEN')
return c.json({ configured: true })
} catch (error: any) {
if (error.message?.includes('[bini-env] Missing required')) {
return c.json(
{
error: 'Configuration error',
details: error.message,
},
500
)
}
return c.json({ error: 'Something went wrong' }, 500)
}
})On failure, the terminal shows:
[bini-env] error Missing required environment variable: "API_KEY" -> Set it in your platform's env config or hosting dashboard.
Production Notes
- Set vars in production -
.envfiles are only loaded during development. In production, set variables in your hosting platform's dashboard. - No platform-specific code -
getEnvandrequireEnvwork on Node.js, Bun, Deno, Vercel Edge, Netlify Edge, and Cloudflare Workers. - Never expose secrets - Never return secret values in API responses. Only return configuration status.
- Use BINI_ for client vars - Use the
BINI_prefix for client-side public config. No prefix for server-only secrets.