Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Environment variables and bindings

Environment variables are settings passed to a program from outside rather than written in the code: the port, a secret key, the database path. This way the same code runs with different settings on your computer and on the server, and secrets stay out of Git.

The .env file

Create a .env file in the project root:

PORT=3000
JWT_SECRET=dev-secret-change-me
DB_FILE=blog.db

Add it to .gitignore if it is not there yet. It is handy to keep a .env.example next to it with the same names but no real values — another developer will see what needs to be filled in.

Loading it in Node.js

Node.js 22 can read such a file itself with the --env-file flag. Change the scripts in package.json:

{
  "scripts": {
    "dev": "tsx watch --env-file=.env src/index.ts",
    "build": "tsc",
    "start": "node --env-file=.env dist/index.js"
  }
}

After restarting npm run dev, the values are available through process.env:

// src/index.ts
const port = Number(process.env.PORT ?? 3000)
 
serve({ fetch: app.fetch, port })

The server starts on the port from .env. A variable's value is always a string, so the port is converted to a number.

The env() function

process.env exists only in Node.js. To keep route code working on any platform, Hono offers the env() function:

// src/routes/admin.ts
import { Hono } from 'hono'
import { env } from 'hono/adapter'
 
const admin = new Hono()
 
admin.get('/config', (c) => {
  const { DB_FILE } = env<{ DB_FILE: string }>(c)
  return c.json({ database: DB_FILE })
})
 
export default admin

On Node.js env(c) reads process.env, on Cloudflare Workers it reads the worker's settings, on Deno it reads Deno.env. The type in angle brackets tells TypeScript which variables to expect.

Bindings and c.env

On Cloudflare Workers, variables and connected services — a D1 database, KV storage — are called bindings and arrive in c.env. Their type is given when the app is created:

// src/index.ts
type Bindings = {
  JWT_SECRET: string
}
 
const app = new Hono<{ Bindings: Bindings }>()
 
app.get('/secret-length', (c) => c.text(String(c.env.JWT_SECRET.length)))

On Node.js, c.env holds the server's internal objects rather than your variables, so the course uses process.env or env(c).

PlatformWhere variables live
Node.jsprocess.env or env(c)
Bunprocess.env, Bun.env or env(c)
DenoDeno.env.get() or env(c)
Cloudflare Workersc.env or env(c)

Common mistake

Reading a secret at module load time with a default value:

// src/auth.ts
export const secret = process.env.JWT_SECRET ?? 'secret'

If you forget to set the variable on the server, the app quietly starts with the weak key secret. Secrets must not have a default: check the variable at startup and stop the program with a clear error if it is missing.

Thanks for reading!

Donate to the site — we work hard for you.