@alis-kit/routers
@alis-kit/routers
A TC39 native decorator-based controller library for Node.js APIs. Supports Express and Fastify, includes Zod-based request validation, structured exception handling, optional Swagger UI generation, and cookie management.
Installation
npm install @alis-kit/routers zod
Note: You also need one of the supported frameworks:
npm install express # for Express npm install fastify # for Fastify
Requirements
- Node.js >= 18
- TypeScript >= 5.2
No reflect-metadata needed — this library uses TC39 native decorators (stage 3):
{
"compilerOptions": {
"experimentalDecorators": false,
"useDefineForClassFields": true,
"strict": true
}
}
Quick Start
import express from "express";
import { RouterKit, ReqController, GetMapping } from "@alis-kit/routers";
@ReqController("/hello")
class HelloController {
@GetMapping("/")
async sayHello() {
return { greeting: "Hello, World!" };
}
}
const app = express();
app.use(express.json());
RouterKit.setup({
framework: "express",
app,
swagger: { enabled: true, path: "/api/docs" },
});
RouterKit.register(HelloController);
app.listen(3000, () => console.log("Server running on port 3000"));
Parameter Injection
Parameter injection is configured via params in the mapping options. Each entry maps to a method parameter by array index.
@ReqController("/users", { authentication: "auth", tag: "User Management" })
class UserController {
@GetMapping("/", {
summary: "Get all users with pagination",
params: [
{ type: "query", schema: PaginationSchema },
],
})
async getAll(query: IPagination) {
return { items: [], page: query.page };
}
@GetMapping("/:id", {
summary: "Get user by ID",
params: [
{ type: "param", key: "id" },
],
})
async getById(id: string) {
return { id, name: "John Doe" };
}
@PostMapping("/", {
summary: "Register new user",
params: [
{ type: "body", schema: CreateUserSchema },
],
})
@HttpStatus(201)
async create(body: ICreateUser) {
return { id: "new-id", ...body };
}
@PutMapping("/:id", {
summary: "Update user",
params: [
{ type: "param", key: "id" },
{ type: "body", schema: UpdateUserSchema },
],
})
async update(id: string, body: IUpdateUser) {
return { id, ...body };
}
}
Param Types
| Type | Description |
|---|---|
"body" |
Injects and validates req.body (optional Zod schema) |
"query" |
Injects and validates req.query (optional Zod schema) |
"param" |
Injects a route parameter by key (e.g. "id" for /:id) |
"req" |
Injects the raw framework request object |
"cookie" |
Injects a CookieSetter instance |
"reply" |
Injects the raw framework reply object (FastifyReply / express.Response) |
API Reference
RouterKit
Central setup class. Configured once at application entry point.
RouterKit.setup(config: RouterKitConfig): void
RouterKit.register(...controllers: Class[]): void
RouterKit.handleNotFound(): void
Configuration:
interface RouterKitConfig {
framework: "express" | "fastify";
app: express.Application | FastifyInstance;
authMiddleware?: MiddlewareFn; // for authentication: "auth"
refreshMiddleware?: MiddlewareFn; // for authentication: "refresh"
swagger?: {
enabled: boolean;
path?: string; // default: "/api/docs"
title?: string; // default: "API Documentation"
version?: string; // default: "1.0.0"
sortBy?: "path" | "method";
searchByPath?: boolean;
searchByMethod?: boolean;
searchByTag?: boolean;
};
logger?: boolean | {
enabled?: boolean;
handler?: (level: "info" | "error", message: string, meta: LoggerMeta) => void;
};
responseEnvelope?: "wrap" | "raw"; // "wrap" (default) or "raw"
globalPrefix?: string; // e.g. "/api"
}
@ReqController(basePath, options?)
Marks a class as a route controller with a base path and default authentication.
@ReqController("/users", { authentication: "auth", tag: "User Management" })
class UserController { ... }
Options:
| Option | Type | Default | Description |
|---|---|---|---|
authentication |
"auth" | "refresh" | false |
false |
Default auth for all routes |
tag |
string |
Derived from class name | Swagger tag |
swagger |
boolean |
true |
Include in Swagger docs |
Method Mapping Decorators
@GetMapping(path, options?)
@PostMapping(path, options?)
@PutMapping(path, options?)
@PatchMapping(path, options?)
@DeleteMapping(path, options?)
Options:
| Option | Type | Description |
|---|---|---|
authentication |
"auth" | "refresh" | false |
Override controller-level auth |
swagger |
boolean |
Include in Swagger docs |
tag |
string |
Override controller tag |
summary |
string |
Swagger summary |
description |
string |
Swagger description |
status |
number |
Default response status code |
params |
ParamDescriptor[] |
Parameter injection descriptors |
Method Decorators
| Decorator | Description |
|---|---|
@UseMiddleware(...fns) |
Applies middleware to a route |
@HttpStatus(code) |
Sets default response status code |
Exceptions
All exceptions extend BaseException and auto-send the appropriate HTTP response.
| Exception | Status | Code |
|---|---|---|
BadRequestException |
400 | BAD_REQUEST |
UnauthorizedException |
401 | UNAUTHORIZED |
ForbiddenException |
403 | FORBIDDEN |
NotFoundException |
404 | NOT_FOUND |
ConflictException |
409 | CONFLICT |
ServerErrorException |
500 | INTERNAL_ERROR |
CookieSetter
Injected via { type: "cookie" } in params. Abstracts cookie operations across frameworks.
cookie.set(key, value, options?) // Set a cookie
cookie.get(key) // Get a cookie value
cookie.delete(key) // Delete a cookie
Full Usage Example
import express from "express";
import { z } from "zod";
import {
RouterKit,
ReqController,
GetMapping, PostMapping, PutMapping, DeleteMapping,
UseMiddleware, HttpStatus,
BadRequestException, NotFoundException, ConflictException,
CookieSetter,
} from "@alis-kit/routers";
// Zod Schemas
const CreateUserSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
age: z.coerce.number().min(1),
});
const UserQuerySchema = z.object({
page: z.coerce.number().default(1),
size: z.coerce.number().default(10),
search: z.string().optional(),
});
// Controllers
@ReqController("/auth", { tag: "Authentication", authentication: false })
class AuthController {
@PostMapping("/login", {
summary: "Login user",
params: [
{ type: "body", schema: z.object({ email: z.string().email(), password: z.string().min(6) }) },
{ type: "cookie" },
],
})
async login(body: { email: string; password: string }, cookie: CookieSetter) {
cookie.set("refresh_token", "token-value", { httpOnly: true, secure: true });
return { accessToken: "jwt-token" };
}
}
@ReqController("/users", { authentication: "auth", tag: "User Management" })
class UserController {
@GetMapping("/", {
summary: "Get all users with pagination",
params: [{ type: "query", schema: UserQuerySchema }],
})
async getAll(query: { page: number; size: number; search?: string }) {
// return await UserService.findAll(query);
}
@GetMapping("/:id", {
summary: "Get user by ID",
params: [{ type: "param", key: "id" }],
})
async getById(id: string) {
// if (!user) throw new NotFoundException("User not found");
}
@PostMapping("/", {
authentication: false,
summary: "Register new user",
params: [{ type: "body", schema: CreateUserSchema }],
})
@HttpStatus(201)
async create(body: { firstName: string; lastName: string; email: string; age: number }) {
// return await UserService.create(body);
}
@PutMapping("/:id", {
summary: "Update user",
params: [
{ type: "param", key: "id" },
{ type: "body", schema: CreateUserSchema.partial() },
],
})
async update(id: string, body: Partial<{ firstName: string; lastName: string; email: string; age: number }>) {
// return await UserService.update(id, body);
}
@DeleteMapping("/:id", {
summary: "Delete user",
params: [{ type: "param", key: "id" }],
})
async delete(id: string) {
// return await UserService.delete(id);
}
}
// App Setup
const app = express();
app.use(express.json());
RouterKit.setup({
framework: "express",
app,
swagger: { enabled: true, path: "/api/docs", title: "My API", version: "1.0.0" },
});
RouterKit.register(AuthController, UserController);
RouterKit.handleNotFound();
app.listen(3000, () => console.log("Server running on port 3000"));
Response Format
Success:
{ "message": "OK", "data": { "_id": "...", "firstName": "Dudi" } }
Error:
{ "message": "User not found", "data": null }
Architecture
This library follows a Functional Core + Decorator Sugar pattern:
core/— All logic lives here: route resolution, parameter injection, Zod validation, error handlingdecorators/— Pure metadata writers using TC39 native decorators (context.metadata)adapters/— Framework-agnostic adapters for Express and Fastify
See ARCHITECTURE-RULES.md for detailed guidelines.
License
MIT