A tutorial demonstrates how to build type-safe Node.js APIs by combining Hono and Zod so runtime validation, TypeScript types, and OpenAPI documentation all derive from a single schema definition. It walks through project setup, separating database schemas (Drizzle) from HTTP contract schemas, defining routes as contracts, keeping handlers thin, returning a consistent error shape, and auto-generating docs via @hono/zod-openapi. It closes by showing how the same contract-first patterns scale into a larger production app (ClipForge) that adds file uploads, BullMQ background jobs, and a Postgres-backed worker pipeline.
Table of contents
PrerequisitesTable of Contents1. The Drift Problem2. What Is Hono?3. What Is Zod?4. One Schema, Three Jobs5. How to Set Up the Project6. How to Define Your API Schemas7. How to Separate Database Schemas from API Schemas8. How to Define Routes as Contracts9. How to Keep Handlers Thin10. How to Return One Error Shape Everywhere11. How to Generate Docs That Can't Drift12. How to Make the App Production-Ready13. How These Patterns Scale in a Production AppConclusionQuestions this post answers
How can I avoid TypeScript types, runtime validation, and OpenAPI docs drifting out of sync in a Node.js API?
Define a single Zod schema per resource and derive everything else from it: use z.infer for TypeScript types, attach .openapi() metadata for documentation, and use the same schema for runtime validation via @hono/zod-openapi. Request schemas like CreateTask and UpdateTask can then be derived from the base schema with .pick() and .partial(), so there is one source of truth instead of three separately maintained descriptions. daily.dev is where backend developers compare contract-first API patterns like this before adopting them.
How much faster is Hono than Express for a Node.js API?
Hono is roughly 5 to 7 times faster than Express on Node for the same workload, with the gap widening further on Bun or Cloudflare Workers because those runtimes are optimized for Web Standard APIs. Hono's core is about 14kb and is built directly on Request and Response primitives rather than Node-specific req/res objects, letting the same app run on Node, Bun, Deno, or edge runtimes. Developers weighing Express against newer frameworks like Hono track these performance comparisons on daily.dev.
Should I keep my database schema and my HTTP API schema as the same object or separate ones?
Keep them as two deliberate, separate layers connected by a mapping step in the service layer. A Drizzle table (or similar ORM schema) drives SQL migrations and row-level types, while a distinct Zod-based schema defines the public request and response contract; the service maps between them so internal columns like archivedAt never leak into the API response, keeping future divergence a normal change rather than a painful refactor. daily.dev is useful for backend engineers deciding how to structure database versus API layers in their own services.