@hono/node-server review
@hono/node-server 2.1.5 runs existing Hono applications on Node.js. Use it when you need a Node listening socket and can control the process configuration. Constructor replacement and incoming-body cleanup are enabled by default. Choose those settings before creating the server, then verify the deployment paths your application uses. The examples below are documentation-derived and unexecuted.
Use @hono/node-server 2.1.5 when existing Hono handlers need a Node listener. Choose the global-constructor setting before creating the server, retain incoming-body cleanup unless both documented exception conditions hold, and test the deployment paths your application uses. These unexecuted examples do not establish capacity or compatibility.
Answers from our run
@hono/node-server or fastify: which should you use?
fastify: Consider Fastify when selecting a framework and you want hooks, plugins and JSON Schema validation. The supplied Fastify README documents those facilities. Choosing it changes Hono's routing layer as well as the server integration; it is not a drop-in Hono adapter. Use @hono/node-server 2.1.5 when existing Hono handlers need a Node listener.
When should you not use @hono/node-server?
Skip this adapter when your hosting platform already invokes Hono's Fetch handler and you do not need a Node listening socket.
Use it if
- Use @hono/node-server 2.1.5 when existing Hono handlers need a Node HTTP listener.
- Use its server-factory option when Hono routes need Node HTTPS.
- Use its static middleware when you can configure the service's working directory and asset URL mapping.
- Use its documented WebSocket integration when you can install ws separately and configure upgrades.
- Skip this adapter when your hosting platform already invokes Hono's Fetch handler and you do not need a Node listening socket.
- Keep Express when preserving existing req/res handlers is the requirement.
- Avoid @hono/node-server 2.1.5 on Node versions below 20; its manifest declares Node >=20.
- Defer adoption when you require measured capacity or verified deployment compatibility first. This guide supplies neither.
Setup reality
@hono/node-server 2.1.5 declares Node >=20 and a Hono peer range of ^4 || ^5.0.0-0 in its published package manifest. Install with npm install --save-exact hono@4 @hono/node-server@2.1.5. The adapter version is pinned; npm resolves a Hono 4 version and saves that resolved version exactly. The peer range is a publisher declaration, not our compatibility test result. Save the JavaScript examples as .mjs files or configure your project to use JavaScript modules.
@hono/node-server 2.1.5 can replace global Request and Response while creating the adapter server, before it listens. The versioned implementation calls getRequestListener() before the server factory, and serve() calls listen() afterward. Assignment occurs when overrideGlobalObjects is not false and global.Request differs from the adapter's Request. Set overrideGlobalObjects: false on each listener that should leave the constructors alone. This skips assignment; it does not restore constructors replaced earlier. The guard checks Request rather than checking Response independently.
@hono/node-server 2.1.5 enables incoming-body cleanup by default. Its setup documentation permits disabling cleanup only when incoming clients are trusted and no response path returns before fully reading a POST body. A reverse proxy alone does not establish both conditions. Check authentication failures and other early responses before changing autoCleanupIncoming. Cleanup is not a guarantee that every unread body will be consumed: the implementation attempts to drain unread HTTP/1 bodies and can close the socket when its drain limits are reached.
Static-file roots in @hono/node-server 2.1.5 resolve relative to the working directory where the application starts. The asset example removes /assets from the URL, so /assets/site.css resolves under ./public as ./public/site.css. Set the service's working directory deliberately. Static middleware serves GET and HEAD requests and passes other methods onward. By default, it skips paths that still contain a percent sign after routing. If you enable allowPercentInPath, put access control before the static middleware on the same route pattern; the README warns that narrower checks can be bypassed.
WebSocket setup in @hono/node-server 2.1.5 requires a separate server such as ws. The README uses WebSocketServer({ noServer: true }), and the supplied implementation throws if the supplied server's options.noServer is not exactly true. Register the upgrade route before starting the listener. The echo example supplies no authentication or origin policy, so add the policies your application requires and test upgrades through your deployment proxy.
These @hono/node-server 2.1.5 examples were not executed. No package installation, runtime test, dependency audit or throughput measurement was performed for this correction. The first example supplies shared imports and app initialization; register later route and middleware fragments before startup, and choose one startup alternative. Start verification with a loopback response, then check early responses with unread bodies, static paths and WebSocket upgrades. The README's benchmark is a publisher result, not our measurement or a capacity estimate for your service.
The product ratings use an editorial 1-5 rubric: 1 means a documented obstacle to the intended job; 2 means substantial gaps; 3 means usable support with caveats; 4 means strong documented coverage of the dimension; 5 requires broader verified evidence than this guide supplies. API stability assesses the documented integration contract, documentation assesses setup coverage, maintenance assesses traceable release activity, and ecosystem assesses documented integrations. Unknown adoption, historical compatibility and future support do not earn points.
Patterns
Start a Hono app on loopback start-node
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/', (c) => c.text('ready'));
serve({
fetch: app.fetch,
hostname: '127.0.0.1',
port: 3000,
}, (info) => {
console.log(`Listening on port ${info.port}`);
});Unexecuted @hono/node-server 2.1.5 example. Later fragments reuse app and serve. Register routes and middleware before startup, and choose one startup variant. Loopback accepts connections within its network namespace; outside clients need another binding or forwarding arrangement.
Skip global-constructor replacement preserve-global-constructors
serve({
fetch: app.fetch,
hostname: '127.0.0.1',
port: 3000,
overrideGlobalObjects: false,
});Unexecuted startup alternative. This skips constructor assignment; it does not restore earlier constructors. Set it on each listener that should leave globals alone. With the default enabled, assignment also depends on global.Request differing from the adapter's Request.
Keep unread-body cleanup enabled retain-body-cleanup
serve({
fetch: app.fetch,
hostname: '127.0.0.1',
port: 3000,
autoCleanupIncoming: true,
});Unexecuted startup alternative that makes the default explicit. Disabling cleanup requires both trusted incoming clients and no response path that leaves a POST body partly unread. Cleanup can close a connection rather than consume the entire remaining body.
Supply an HTTPS server factory serve-https
import { createServer } from 'node:https';
import { readFileSync } from 'node:fs';
serve({
fetch: app.fetch,
hostname: '127.0.0.1',
port: 3443,
createServer,
serverOptions: {
key: readFileSync('./certs/local-key.pem'),
cert: readFileSync('./certs/local-cert.pem'),
},
});Unexecuted startup alternative. Supply your own certificate and key files. These relative paths resolve from the working directory. This example terminates TLS in Node.
Map an asset URL to a directory map-static-prefix
import { serveStatic } from '@hono/node-server/serve-static';
app.use('/assets/*', serveStatic({
root: './public',
rewriteRequestPath: (path) => path.replace(/^\/assets/, ''),
}));Unexecuted middleware fragment. Register it before startup. It maps /assets/site.css to ./public/site.css relative to the working directory. Only GET and HEAD are served. Paths still containing a percent sign after routing are skipped by default. Put authorization before this middleware on the same /assets/* pattern.
Read the directly connected peer inspect-socket-peer
import { getConnInfo } from '@hono/node-server/conninfo';
app.get('/peer', (c) => {
const { remote } = getConnInfo(c);
return c.json({ address: remote.address ?? null });
});Unexecuted route fragment. Register it before startup and restrict this diagnostic endpoint before deployment. The socket peer may be a proxy; its address does not authenticate an end user.
Listen on a UNIX domain socket own-listening-socket
import { createAdaptorServer } from '@hono/node-server';
const server = createAdaptorServer({
fetch: app.fetch,
overrideGlobalObjects: false,
});
server.on('error', (error) => console.error(error));
server.listen('/tmp/hono-example.sock');Unexecuted startup alternative. createAdaptorServer() constructs the listener and returns the server without calling listen(). This example explicitly skips constructor replacement during construction. Choose a socket location and permissions your service owns, and account for stale socket files.
Attach a separate WebSocket server upgrade-websocket
import { upgradeWebSocket } from '@hono/node-server';
import { WebSocketServer } from 'ws';
app.get('/ws', upgradeWebSocket(() => ({
onMessage(event, ws) {
ws.send(event.data);
},
})));
const wss = new WebSocketServer({ noServer: true });
serve({
fetch: app.fetch,
hostname: '127.0.0.1',
port: 3000,
websocket: { server: wss },
});Unexecuted startup alternative. Install ws separately and add @types/ws when compiling TypeScript. The README configures noServer: true, and the versioned implementation throws when that option is not exactly true. This echo route has no authentication or origin policy; test upgrades through your proxy.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| fastify | npm | Consider Fastify when selecting a framework and you want hooks, plugins and JSON Schema validation. The supplied Fastify README documents those facilities. Choosing it changes Hono's routing layer as well as the server integration; it is not a drop-in Hono adapter. |
| express | npm | Keep Express when you need its req/res route handlers and app.listen() startup. The supplied Express README demonstrates that contract. Switching from Hono to Express changes the framework rather than only the Node adapter. |
More web backend guides
urllib3 · requests · anyio · ws · httpx · starlette · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.