Thin wrapper around the router of web frameworks like Express and Hono, offering OpenAPI typesafety and seamless integration with validation libraries such as Valibot and Zod
npm install openapi-ts-router> Status: Experimental
openapi-ts-router is a thin wrapper around the router of web frameworks like Express and Hono, offering OpenAPI typesafety and seamless integration with validation libraries such as Valibot and Zod.
- Full type safety for routes, methods, params, body and responses
- Runtime validation using Zod/Valibot
- Catches API spec mismatches at compile time
- Zero manual type definitions needed
- Seamless integration with existing Express/Hono applications
- Enforces OpenAPI schema compliance at both compile-time and runtime
- Express Petstore
- Hono Petstore
openapi-ts-router provides full type-safety and runtime validation for your Express API routes by wrapping a Express router:
> Good to Know: While TypeScript ensures compile-time type safety, runtime validation is equally important. openapi-ts-router integrates with Zod/Valibot to provide both:
>
> - Types verify your code matches the OpenAPI spec during development
> - Validators ensure incoming requests match the spec at runtime
``ts
import { Router } from 'express';
import { createExpressOpenApiRouter } from 'openapi-ts-router';
import { zValidator } from 'validation-adapters/zod';
import * as z from 'zod';
import { paths } from './gen/v1'; // OpenAPI-generated types
import { PetSchema } from './schemas'; // Custom reusable schema for validation
export const router: Router = Router();
export const openApiRouter = createExpressOpenApiRouter
// GET /pet/{petId}
openApiRouter.get('/pet/{petId}', {
pathValidator: zValidator(
z.object({
petId: z.number() // Validate that petId is a number
})
),
handler: (req, res) => {
const { petId } = req.valid.params; // Access parsed & validated params
res.send({ name: 'Falko', photoUrls: [] });
}
});
// POST /pet
openApiRouter.post('/pet', {
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
handler: (req, res) => {
const { name, photoUrls } = req.body; // Access validated body data
res.send({ name, photoUrls });
}
});
// TypeScript will error if route/method doesn't exist in OpenAPI spec
// or if response doesn't match defined schema
`
openapi-ts-router provides full type-safety and runtime validation for your HonoAPI routes by wrapping a Hono router:
> Good to Know: While TypeScript ensures compile-time type safety, runtime validation is equally important. openapi-ts-router integrates with Zod/Valibot to provide both:
>
> - Types verify your code matches the OpenAPI spec during development
> - Validators ensure incoming requests match the spec at runtime
> Note: Hono's TypeScript integration provides type suggestions for c.json() based on generically defined response types, but it doesn't enforce these types at compile-time. For example, c.json('') won't raise a type error even if the expected type is { someType: string }. This is due to Hono's internal use of TypedResponse, which infers but doesn't strictly enforce the passed generic type. Hono Discussion
`ts
import { Hono } from 'hono';
import { createHonoOpenApiRouter } from 'openapi-ts-router';
import { zValidator } from 'validation-adapters/zod';
import * as z from 'zod';
import { paths } from './gen/v1'; // OpenAPI-generated types
import { PetSchema } from './schemas'; // Custom reusable schema for validation
export const router = new Hono();
export const openApiRouter = createHonoOpenApiRouter
// GET /pet/{petId}
openApiRouter.get('/pet/{petId}', {
pathValidator: zValidator(
z.object({
petId: z.number() // Validate that petId is a number
})
),
handler: (c) => {
const { petId } = c.req.valid('param'); // Access validated params
return c.json({ name: 'Falko', photoUrls: [] });
}
});
// POST /pet
openApiRouter.post('/pet', {
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
handler: (c) => {
const { name, photoUrls } = c.req.valid('json'); // Access validated body data
return c.json({ name, photoUrls });
}
});
// TypeScript will error if route/method doesn't exist in OpenAPI spec
// or if response doesn't match defined schema
`
We intentionally only type success responses (2xx) while leaving error responses out. Hereโs why:
Errors should be handled via exceptions & middleware
Instead of typing every possible error response inline, we believe that handling errors globally in middleware provides clearer, more maintainable code.
๐ Example: Hono Example (Docs) & Express Example (Docs)
Inline error responses require as any
Since .send() only expects success types, explicit casting is required to enforce an error response:
`ts`
res.status(500).send({
code: '#ERR_XYZ',
message: 'Error Message'
} satisfies TOperationResponseContent
Express and Hono don't infer status codes for res.send() / c.json()
Since we canโt infer the value of the res.status() / c.json() method call, res.send() / c.json() is typed as a union of success response types. For example, it could be:
`ts`
{ message: 'Success Body of 200' } | { message: 'Success Body of 201' }
To enforce a specific success type, use satisfies
Example of explicitly enforcing a 201 response type:
`ts`
res.status(201).send({
id: 123
} satisfies TOperationResponseContent
Hono's TypeScript integration provides type suggestions for c.json() based on generically defined response types, but it doesn't enforce these types at compile-time. For example, c.json('') won't raise a type error even if the expected type is { someType: string }. This is due to Hono's internal use of TypedResponse, which infers but doesn't strictly enforce the passed generic type. Hono Discussion
To enforce a specific success type, use satisfies
Example of explicitly enforcing a 201 response type:
`ts``
c.json(
{
id: 123
} satisfies TOperationResponseContent
201
);