zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Ecosystem integrations

HonoSupported

Hono is a small, fast, standards-based router. @zmdb/web's WebApplication exposes fetch(request) and handle(req) and owns no server, which makes the two compose rather than compete — you can mount zmdb inside Hono, or use zmdb's data layer under Hono routes and skip @zmdb/web entirely.

zmdb data layer, Hono routes#

The lightest combination, and a good default if you already like Hono:

import { Hono } from 'hono';
import { assert } from '@zmdb/validator';
import { type CreateDTO } from '@zmdb/orm';

const api = new Hono();

api.get('/posts', async c => c.json(await postRepo.list({ page: { limit: 20 } })));

api.post('/posts', async c => {
  const dto = assert<CreateDTO<Post>>(await c.req.json());
  return c.json(await postRepo.create(dto), 201);
});

No decorators, no container, no @zmdb/web at all. The schema, compiler, repository and validators are independent of the web package.

Mount @zmdb/web inside Hono#

Because WebApplication.fetch takes and returns web-standard Request/Response, it mounts as a Hono handler:

const zmdbApp = createApp(AppModule);
await zmdbApp.init();

const hono = new Hono();
hono.all('/api/*', c => zmdbApp.fetch(c.req.raw));
hono.get('/health', c => c.text('ok'));

Use this when you want zmdb's DI and OpenAPI for the API surface and Hono's middleware for everything around it — CORS, static assets, compression, streaming.

📝 Note

Your controllers see the full path, so a controller behind /api/* must be declared as @Controller('/api/posts'). WebApplication does not know it is mounted and does not strip a prefix.

Hono middleware fills real gaps#

This is the strongest argument for the combination. Several cross-cutting features @zmdb/web does not ship are one Hono middleware away:

Gap in @zmdb/webHono
Compressioncompress()
Static filesserveStatic()
CSRFcsrf()
Rate limiting, CORS, secure headersmiddleware

Put those outside the mount, where Hono owns the Response. For an application-owned response stream, @zmdb/web itself provides stream().

Choosing between them#

Hono@zmdb/web
Runtime dependencieshonozero
Stylefunctional handlersclasses + decorators
DI containernonebuilt-in
Modulesnonemodule graph
OpenAPIvia @hono/zod-openapinative
Streamingyesno
Middleware ecosystemlargenone

If your application is a handful of routes, Hono plus zmdb's data layer is less machinery. If it has services, layered dependencies and a generated API contract, @zmdb/web earns its structure.

Validation#

assert<T> works as a Hono validator with no adapter:

api.post('/posts', async c => {
  const dto = assert<CreateDTO<Post>>(await c.req.json());
  // ...
});

Catch validation failures in app.onError and map them to a 400. Without the transformer, the generic call instead throws runtime type witness required in test/fallback mode, so keep a build-path canary:

it('the transformer is running', () => {
  expect(is<{ id: number }>({ id: 'x' })).toBe(false);
});

On the edge#

Hono targets Workers, Deno and Bun; so does zmdb's read path, with an HTTP driver. Bun still needs a separate build route. React Native uses the withZmdb Metro wrapper. Keep the canary against either built artefact.

---

See also: Standalone Applications · Streaming · Serverless Performance