Deploy and operate Hono
Deploy to Node and Workers
Use the correct adapter and deployment entry point while keeping route code shared.
Node and Workers both speak Request and Response. They differ in process lifecycle, sockets, filesystem access, and platform limits.
The Node version owns a listener and graceful shutdown:
import { serve } from '@hono/node-server'
import { createApp } from './app.js'
const app = createApp(/* node deps */)
const server = serve({ fetch: app.fetch, port: 3000 })
process.on('SIGTERM', () => server.close())
The Worker version exports fetch and reads bindings from the platform:
import { createApp } from './app.js'
export default {
fetch(request, env, ctx) {
const app = createApp({ bookmarks: new D1BookmarkStore(env.DB) })
return app.fetch(request, env, ctx)
}
}
Shared route code lives in createApp(). Entry files only wire runtime-specific dependencies.
Smoke-test both targets with the same curl:
curl -s http://localhost:3000/health
curl -s http://localhost:8787/health
Each should return {"ok":true}. Then hit bookmarks:
curl -s http://localhost:3000/bookmarks
On Node with Postgres wired, you get [] or a JSON array. On Workers without a D1 binding in wrangler.jsonc, the same curl may return 500 {"error":"Internal error"} because env.DB is undefined inside the store. Fix by declaring the D1 binding in wrangler.jsonc, not by try/catch around every query.
Before you deploy, audit npm dependencies. A package that reads the filesystem fails on Workers. Run wrangler dev and node server.js against the same Vitest suite locally.
Node uses .env for DATABASE_URL. Workers read bindings from wrangler.jsonc. Document both paths so deployers know where each value lives.
After deploy, run the same three curls against production URLs and paste status codes into the portability report. Staging on Workers and production on Node is common; the report should say so explicitly.
Wrangler tail during a bookmark POST should show one structured log line per request, not a dump of the JSON body.
Compare health check latency on both runtimes. Workers cold starts affect the first curl after idle; Node keeps the process warm.
Do not copy the Node entry into a Worker and patch globals one at a time. Split entry files early.
Try this on your own project: run the full Vitest suite, then smoke-test both entry points with the same three curl commands.
Lesson completed