Type Safety Layer for Express APIs
Runtime Validation • OpenAPI Docs • Full Type Inference
Building Express APIs involves three tedious, duplicated tasks:
- Type definitions for your request/response objects
- Runtime validation to ensure incoming data is valid
- API documentation for consumers to understand your endpoints
Keeping these in sync is a maintenance nightmare. Instead of juggling multiple libraries and duplicating schemas, tyex
lets you define everything once and get all three benefits automatically.
- 🔄 Single source of truth for types, validation, and documentation
- 🚀 No new frameworks to learn - it's just Express
- 🔍 Full TypeScript inference for request params, query, and body
- ✅ Runtime validation with automatic coercion (strings to numbers, etc.)
- 📚 OpenAPI documentation auto-generated from your handlers
- 🔌 Async handler support with proper error handling
npm install tyex @sinclair/typebox
import express from "express";
import tyex from "tyex";
import { Type } from "@sinclair/typebox";
import swaggerUi from "swagger-ui-express";
const app = express();
app.use(express.json());
// Define your schema with TypeBox
const UserSchema = Type.Object({
id: Type.Integer(),
username: Type.String(),
});
// Create a route with tyex.handler
app.get(
"/api/users/:id",
tyex.handler(
{
parameters: [
{
name: "id",
in: "path",
required: true,
schema: Type.Integer(),
},
],
responses: {
"200": {
description: "User details",
content: {
"application/json": {
schema: UserSchema,
},
},
},
},
},
async (req, res) => {
const user = await getUser(req.params.id);
res.json(user);
},
),
);
// Add OpenAPI documentation endpoint
app.get("/api-spec", tyex.openapi());
// Optional: Add Swagger UI
app.use(
"/api-docs",
swaggerUi.serve,
swaggerUi.setup(null, { swaggerOptions: { url: "/api-spec" } }),
);
app.listen(3000);
The tyex.handler
function wraps your Express handlers with two key benefits:
- Type-checking - Your handler receives fully typed request objects
- Runtime validation - Incoming requests are validated against your schema
tyex.handler(
{
// OpenAPI 3 operation object
parameters: [...],
requestBody: {...},
responses: {...},
},
(req, res) => {
// Your regular Express handler
}
)
The tyex.openapi()
middleware automatically generates an OpenAPI document from your handlers:
// Basic usage
app.get("/api-spec", tyex.openapi());
// With additional configuration
app.get(
"/api-spec",
tyex.openapi({
document: {
openapi: "3.0.3",
info: {
title: "My API",
version: "1.0.0",
},
servers: [
{
url: "https://api.example.com",
},
],
},
}),
);
Since OpenAPI schemas are a superset of JSON Schema, tyex
provides helper functions for common OpenAPI-specific patterns:
import { TypeOpenAPI } from "tyex";
TypeOpenAPI.Nullable(Type.String()); // { type: 'string', nullable: true }
TypeOpenAPI.StringEnum(["admin", "user"]); // enum: ['admin', 'user']
TypeOpenAPI.Options(Type.Number(), { default: 10 }); // Default values with proper type inference
Validation errors are passed to Express's error handling middleware:
import { ValidationError } from "tyex";
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
return res.status(400).json({ errors: err.errors });
}
next(err);
});
See the examples directory for full working examples, including the "kitchen sink" example with authentication, error handling, and more.
- Only
application/json
request bodies are supported currently - Response schemas are used for types and documentation, but aren't validated