@phila/philaroute
v1.0.14
Published
Basic HTTP router for AWS API Gateway / Proxy Lambdas with composable pipelines built with TypeScript.
Downloads
9
Readme
philaroute
A lightweight, composable HTTP router for Node.js Lambda/API Gateway environments, written in TypeScript. Supports composable middleware pipelines and CORS out of the box.
Features
- TypeScript-first API with full type definitions
- Simple route and method registration
- Functional middleware pipeline composition
- Built-in input validation (environment, query params, request body)
- Built-in CORS support for Lambda/API Gateway
- Automatic error handling with proper HTTP status codes
Installation
npm install philarouteQuick Start
import { Router, http } from 'philaroute';
const router = Router({ cors: { 'Access-Control-Allow-Origin': '*' } });
const hello = router.path('/hello');
hello.get([
async (acc) => {
acc.data.message = 'Hello World!';
return acc;
}
]);
// Lambda handler
export const main = async (event) => {
return router.routeToPath(event);
};Core Concepts
The Accumulator Pattern
All request data flows through a RestAccumulator object that gets passed through your pipeline functions:
type RestAccumulator<TData = Record<string, any>> = {
request?: {
params?: { [key: string]: string }, // Query string parameters
body?: any, // Parsed JSON request body
headers?: { [key: string]: string }, // HTTP headers
mvHeaders?: { [key: string]: string[] }, // Multi-value headers
},
data: TData, // Pipeline state (see note below)
response: {
statusCode?: number,
headers?: { [key: string]: string },
isBase64Encoded?: boolean,
body?: string | Record<string, any> | any[]
}
}Important: If response.body is not explicitly set, the data object becomes the response body automatically. This allows pipelines to build up response data without explicitly setting the body.
Pipeline Functions
Each function in a pipeline receives the accumulator and returns a modified version:
type PipelineFn = (acc: RestAccumulator) => Promise<RestAccumulator>Functions execute sequentially. Each receives the output of the previous function.
Type-Safe Pipelines
For full compile-time type safety, use typedPipeline which tracks the accumulator shape through each transformation:
import { typedPipeline, TransformFn } from 'philaroute';
type Input = { value: number };
type Output = { value: number; doubled: number };
const double: TransformFn<Input, Output> = async (acc) => ({
...acc,
doubled: acc.value * 2
});
const pipeline = typedPipeline(double);
const result = await pipeline({ value: 5 });
// result.doubled is typed as numberThe typedPipeline uses TypeScript's tuple inference to validate that each function's output type matches the next function's input type at compile time.
API Reference
Router
Create a router instance with optional CORS configuration:
import { Router } from 'philaroute';
const router = Router({
cors: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
});Registering Routes
const users = router.path('/users');
users.get([...pipeline]); // GET /users
users.post([...pipeline]); // POST /users
users.put([...pipeline]); // PUT /users
users.patch([...pipeline]); // PATCH /users
users.delete([...pipeline]); // DELETE /usersOPTIONS requests are handled automatically with a 204 No Content response.
Dispatching Requests
// In your Lambda handler
export const handler = async (event) => {
return router.routeToPath(event);
};Validation
Built-in validators throw AssertionError on failure, which the router converts to 400 Bad Request responses. All validators provide compile-time type inference for the validated fields.
Environment Variables
import { validate } from 'philaroute';
users.get([
validate.environment(['DATABASE_URL', 'API_KEY']),
async (acc) => {
// TypeScript knows these are strings:
const dbUrl: string = acc.data.valid.environment.DATABASE_URL;
const apiKey: string = acc.data.valid.environment.API_KEY;
return acc;
}
]);Query Parameters
users.get([
validate.parameters(['page', 'limit']),
async (acc) => {
// TypeScript knows these are strings:
const page: string = acc.data.valid.parameters.page;
const limit: string = acc.data.valid.parameters.limit;
return acc;
}
]);Request Body
Validate body fields with type checking. The schema maps to TypeScript types:
users.post([
validate.body({
name: 'string',
age: 'number',
active: 'boolean',
metadata: 'object',
tags: 'array'
}),
async (acc) => {
// TypeScript infers the correct types:
const name: string = acc.data.valid.body.name;
const age: number = acc.data.valid.body.age;
const active: boolean = acc.data.valid.body.active;
return acc;
}
]);Response Handling
Explicit Response
Use http.response() to set response properties:
import { http } from 'philaroute';
users.post([
validate.body({ name: 'string' }),
async (acc) => {
const user = await createUser(acc.data.valid.body);
acc.data.user = user;
return acc;
},
http.response({
statusCode: 201,
body: { success: true }
})
]);Implicit Response
If you don't set response.body, the data object is returned:
users.get([
async (acc) => {
acc.data.users = await fetchUsers();
acc.data.total = 100;
return acc;
}
// Response body will be: { users: [...], total: 100 }
]);Error Handling
The router automatically handles errors:
| Error Type | HTTP Status | |-----------|-------------| | Path not found | 404 Not Found | | Method not allowed | 405 Method Not Allowed | | AssertionError (validation) | 400 Bad Request | | Any other error | 500 Internal Server Error |
CORS headers are included in all error responses.
Complete Example
import { Router, validate, http } from 'philaroute';
const router = Router({
cors: { 'Access-Control-Allow-Origin': '*' }
});
const users = router.path('/users');
users.get([
validate.parameters(['page']),
async (acc) => {
const page = parseInt(acc.data.valid.parameters.page, 10);
acc.data.users = await db.users.findMany({ skip: page * 10, take: 10 });
return acc;
}
]);
users.post([
validate.body({ name: 'string', email: 'string' }),
async (acc) => {
const user = await db.users.create(acc.data.valid.body);
acc.data.user = user;
return acc;
},
http.response({ statusCode: 201 })
]);
export const handler = (event) => router.routeToPath(event);Module Structure
| Module | Purpose |
|--------|---------|
| index.ts | Router factory, route registration, request dispatch |
| fn.ts | Pipeline composition (pipeline, typedPipeline, restPipeline) |
| parse.ts | AWS event to RestAccumulator transformation |
| http.ts | Response formatting and HTTP status codes |
| validate.ts | Input validation middleware with type inference |
| types.ts | Core TypeScript type definitions |
| aws.ts | AWS API Gateway v1 payload interface |
Development
npm run build # Build TypeScript
npm test # Run tests
npm run lint # Lint code
npm run format # Format codeLicense
MIT © City of Philadelphia
