Fully-Typed HTTP Router for AWS Lambda with Middy and Zod
Build type-safe, validated REST APIs on AWS Lambda without the boilerplate. This monorepo demonstrates a lightweight routing solution that brings the DX of modern web frameworks to serverless functions.
When building REST APIs on AWS Lambda, you're faced with a dilemma:
- Lambda per endpoint? Leads to configuration sprawl and cold start overhead
- Single Lambda monolith? Loses the clarity and type safety of individual handlers
This project solves that by providing a thin, type-safe routing layer that:
- β Keeps your handlers small, focused, and fully typed
- β Automatically validates requests and responses with Zod
- β Infers TypeScript types from your schemas (no manual typing!)
- β Provides consistent error handling and response formatting
- β Integrates seamlessly with Middy middleware ecosystem
Define your route once, get end-to-end types everywhere:
export const getTodo = createRoute({
method: "GET",
path: "/todos/{id}",
schemas: {
params: z.object({ id: z.string().uuid() }),
response: z.object({
id: z.string().uuid(),
title: z.string(),
completed: z.boolean(),
}),
},
handler: async (event, ctx) => {
// event.params.id is typed as string
// return type must match response schema
const todo = await ctx.ddb.get(/* ... */);
return todo; // β
Validated against schema
},
});Zod schemas validate:
- Path parameters (
/todos/{id}) - Query strings (
?status=completed) - Request bodies
- Response payloads
Invalid data? Automatic 400 response with detailed errors.
All responses follow a consistent structure:
{
"success": true,
"data": { "id": "...", "title": "..." },
"meta": {
"timestamp": "2025-10-11T12:00:00.000Z",
"requestId": "abc-123"
}
}Use semantic HTTP errors that are automatically caught and formatted:
if (!todo) {
throw new NotFoundError("Todo not found");
}
// β 404 response with proper error structureThis is a Turborepo monorepo with:
lambda-http-router/
βββ packages/
β βββ http-router/ # π¦ Reusable router package
β βββ create-route.ts # Route factory with validation
β βββ route-parser.ts # Request/response middleware
β βββ error-handler.ts # Standardized error handling
β βββ types.ts # Core TypeScript types
β
βββ api/ # π¬ Demo API (Todos CRUD)
βββ lambda/ # Lambda handler entry point
βββ routes/ # Route definitions
βββ models/ # Data models
βββ cdk/ # Infrastructure (optional)
The @repo/http-router package is framework-agnostic and can be extracted to any Lambda project.
- Node.js 18+
- npm 10+
# Clone the repository
git clone https://github.com/silviuglv/lambda-http-router.git
cd lambda-http-router
# Install dependencies
npm installnpm test# Synthesize CloudFormation template
npx cdk synth
# Deploy the stack
npx cdk deployCreate a new file in api/src/routes/:
import { createRoute } from "@repo/http-router";
import { z } from "zod";
export const createItem = createRoute({
method: "POST",
path: "/items",
schemas: {
body: z.object({
name: z.string().min(1),
price: z.number().positive(),
}),
response: z.object({
id: z.string(),
name: z.string(),
price: z.number(),
}),
},
handler: async (event, ctx) => {
// event.body is typed and validated
const item = await ctx.ddb.put({
id: crypto.randomUUID(),
...event.body,
});
return item;
},
});Add it to api/src/routes/index.ts:
import { defineRoutes } from "@repo/http-router";
import { createItem } from "./create-item";
export const routes = defineRoutes(
createItem,
// ... other routes
);Your route is now:
- β Fully typed from request to response
- β Validated against Zod schemas
- β Integrated with your Lambda handler
- β Protected by error handling middleware
Inject dependencies (database clients, config, etc.) into all handlers:
// In api/src/lambda/execute-request.ts
export const context = {
ddb: dynamoDbClient,
env: { tableName: process.env.TABLE_NAME },
};
// Register the context type
declare module "@repo/http-router" {
interface Register {
context: typeof context;
}
}
// Apply via middleware
export const handler = middy()
.use(httpContext(context)) // β Injects context
.handler(httpRouterHandler({ routes }));Now ctx is typed and available in all route handlers.
The project includes comprehensive tests for:
- Route creation and validation
- Request/response parsing
- Error handling
- Schema validation failures
# Run all tests
npm test
# Test specific workspace
cd packages/http-router && npm test
cd api && npm test
# Lint
npm run lintFor detailed architecture, examples, and best practices, see:
This is a demonstration project, but contributions are welcome!
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests (
npm test) - Submit a pull request
MIT License - see LICENSE file for details
Silviu Glavan
- Website: silviu.dev
- GitHub: @silviuglv
If this project helped you build better Lambda APIs, give it a star on GitHub!