Routing and configuration
Configure bindings and types
Declare platform resources in wrangler.jsonc and access them through a typed environment instead of global variables or credentials.
A binding is how a Worker reaches a Cloudflare resource. You declare it in wrangler.jsonc, and it appears as a property on env. D1 databases, KV namespaces, R2 buckets, Queues, Durable Objects, and other Workers all connect this way.
Here is what the D1 binding for Link Vault looks like:
{
"d1_databases": [
{ "binding": "DB", "database_name": "link-vault", "database_id": "..." }
]
}
binding is the name your code sees, env.DB. database_name and database_id say which real database sits behind that name. You get the ID from wrangler d1 create, which we run in the next module.
After saving, regenerate the types:
npx wrangler types
Open worker-configuration.d.ts and you find DB: D1Database inside the Env interface. In a Hono route, c.env.DB now autocompletes.
No credentials in your code
Think of a binding as capability injection. Your code gets a ready-to-use object for one named resource. There is no connection string to read, no API token to store, nothing to leak into a log. If a value never enters your code, you can’t accidentally print it.
Same name, different resources
Keep the binding name identical in every environment. env.DB is env.DB in development, staging, and production. Only the database_id behind it changes, and that lives in the config, per environment.
Your application code never knows which environment it’s in. The configuration expresses the deployment boundary. That’s the cleanest separation I know.
What generated types prove
wrangler types proves that your code and your config agree at compile time. env.DB exists and has the D1 methods.
It does not prove the database exists in that account, or that the schema is there. A typo in database_id compiles fine and fails at the first query. So add a smoke request that touches each critical binding, and run it after every deployment. For D1, SELECT 1 is enough.
Don’t cache secret-derived clients globally
One trap to avoid. Say you build an API client from a secret and store it at module scope, so it’s created once per isolate:
const client = createClient(env.API_TOKEN) // don't do this
Bindings change on deployment, but an isolate created by the previous deployment may keep running for a while. That isolate still holds a client built with the old secret. Build the client from the current env inside the request instead. It’s cheap, and it’s always right.
Add the D1 binding above to your wrangler.jsonc now, with a placeholder ID for the moment, and run npx wrangler types. Check that DB shows up in the generated Env. We fill in the real ID when we create the database.
Lesson completed