Hono and Web Standard Runtimes

Express 24,430 predates fetch. Its req is a Node IncomingMessage and its res a Node ServerResponse, objects that exist nowhere but Node. Hono 383,227 starts from the other end: a handler receives a context wrapping a web Request and returns a web Response, the classes your React 7,897 code already uses. That is why one file runs on Cloudflare Workers 2 , Deno 60,577 , Bun 73,307 , Lambda and Node, the last through @hono/node-server. The package declares zero dependencies, so a Worker bundle stays in the tens of kilobytes.

A Hono app that needs no server to answer (hono-demo.js)JavaScript
const app = new Hono();
app.use('*', async (c, next) => {           // middleware is a Koa-style cascade
  const t = performance.now();
  await next();
  c.res.headers.set('x-response-time', `${(performance.now() - t).toFixed(1)}ms`);
});
app.get('/books/:id', (c) => {
  const book = books.find((b) => b.id === c.req.param('id'));
  if (!book) return c.json({ error: { code: 'not_found' } }, 404);
  return c.json({ data: book });            // status is the second argument
});
// No listen(), no port, no supertest: hand the app a Request, read the Response.
const res = await app.request('/books/bk_3');
console.log(res.status, res.headers.get('content-type'), res.headers.get('x-response-time'));
console.log(await res.text(), (await app.request('/nope')).status);
Output
200 application/json 2.2ms
{"data":{"id":"bk_3","title":"Book number 3","authorId":"au_3","year":1952}} 404

app.request() is why Hono tests run in milliseconds: the app is a function from Request to Response, so a test never opens a socket. Handlers are async and c.req.json() returns a promise, because a web Request body is a stream, not a pre-parsed object — the one habit Express programmers must unlearn.

The trade is ecosystem: Express middleware does not run here, so you use hono/cors, hono/jwt and hono/logger, and write the rest yourself.