@alis-kit/routers
v3.0.1
Published
TC39 decorator-based controller library for Node.js APIs. Supports Express and Fastify with Zod validation.
Maintainers
Readme
@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.
🚧 v3.0.0 in development — Stackable decorators (
@RestController,@ReqBody,@ReqQuery,@Response, etc.) are being introduced. See Sprint 2 Plan and ADR-002 for details.
Table of Contents
- Installation
- Requirements
- Quick Start (v2.x)
- Coming in v3.0 — Stackable Decorators
- API Reference (v2.x — current)
- Response Format
- Architecture
- Development
- License
Installation
npm install @alis-kit/routers zodNote: You also need one of the supported frameworks:
npm install express # for Express npm install fastify # for Fastify
Requirements
- Node.js >= 20.19.0
- TypeScript >= 5.6
No reflect-metadata needed — this library uses TC39 native decorators (stage 3):
{
"compilerOptions": {
"experimentalDecorators": false,
"useDefineForClassFields": true,
"strict": true
}
}Quick Start (v2.x)
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"));🚀 Coming in v3.0 — Stackable Decorators
Sprint 2 introduces stackable decorators that separate each concern into its own decorator.
@ReqController is replaced by @RestController, @Authentication, @Tag, and @Description.
Parameter injection via MappingOptions.params is replaced by the @ReqBody, @ReqQuery, @ReqParam, etc. decorators.
import express from "express";
import { z } from "zod";
import {
RouterKit,
RestController, Authentication, Tag, Description,
GetMapping, PostMapping, PutMapping, DeleteMapping,
ReqBody, ReqQuery, ReqParam, ReqCookie,
Response, HttpStatus,
BadRequestException, NotFoundException,
} from "@alis-kit/routers";
// ── Zod Schemas ───────────────────────────────────────────────────
const PaginationSchema = z.object({
page: z.coerce.number().default(1),
size: z.coerce.number().default(10),
});
const CreateUserSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
age: z.coerce.number().min(1),
});
type ICreateUser = z.infer<typeof CreateUserSchema>;
type IPagination = z.infer<typeof PaginationSchema>;
// ── Controllers ───────────────────────────────────────────────────
@RestController("/auth")
@Tag("Authentication")
@Description("Public authentication endpoints")
class AuthController {
@PostMapping("/login")
@Description("Login with email and password")
@ReqBody(z.object({ email: z.string().email(), password: z.string().min(6) }))
@ReqCookie()
@Response(200, "Login successful, refresh token set in cookie")
async login(body: { email: string; password: string }, cookie: CookieSetter) {
cookie.set("refresh_token", "token-value", { httpOnly: true, secure: true });
return { accessToken: "jwt-token" };
}
}
@RestController("/users")
@Authentication("auth")
@Tag("User Management")
@Description("User management CRUD")
class UserController {
@GetMapping("/")
@Description("List users with pagination")
@ReqQuery(PaginationSchema)
@Response(200, "List of users")
async getAll(query: IPagination) {
return { items: [], page: query.page, size: query.size };
}
@GetMapping("/:id")
@Description("Get user detail by ID")
@ReqParam("id")
@Response(200, "User detail")
@Response(404, "User not found")
async getById(id: string) {
// if (!user) throw new NotFoundException("User not found");
return { id, name: "John Doe" };
}
@PostMapping("/")
@HttpStatus(201)
@ReqBody(CreateUserSchema)
@Response(201, "User created successfully")
@Response(400, "Validation failed")
async create(body: ICreateUser) {
return { id: "new-id", ...body };
}
@PutMapping("/:id")
@ReqParam("id")
@ReqBody(CreateUserSchema.partial())
@Response(200, "User updated successfully")
async update(id: string, body: Partial<ICreateUser>) {
return { id, ...body };
}
@DeleteMapping("/:id")
@ReqParam("id")
@Response(200, "User deleted successfully")
async delete(id: string) {
return { deleted: true, 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: "2.0.0" },
});
RouterKit.register(AuthController, UserController);
RouterKit.handleNotFound();
app.listen(3000, () => console.log("Server running on port 3000"));API Reference (v2.x — current)
RouterKit
Central setup class. Configured once at the application entry point.
RouterKit.setup(config: RouterKitConfig): void
RouterKit.register(...controllers: Class[]): void
RouterKit.handleNotFound(): voidConfiguration:
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?)
⚠️ Deprecated in v3.0 — use
@RestController+@Authentication+@Taginstead.
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 (v2.x) |
Method Decorators
| Decorator | Description |
|---|---|
| @UseMiddleware(...fns) | Applies middleware to a route |
| @HttpStatus(code) | Sets default response status code |
Exceptions
All exceptions extend BaseException and automatically 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 the @ReqCookie() decorator (v3) or via { type: "cookie" } in params (v2).
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 cookieResponse Format
Success (wrap mode):
{ "message": "OK", "data": { "id": "...", "firstName": "Dudi" } }Success (raw mode):
{ "id": "...", "firstName": "Dudi" }Error:
{ "statusCode": 404, "code": "NOT_FOUND", "message": "User not found", "details": 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
Development
pnpm install # Install dependencies
pnpm build # Compile TypeScript
pnpm test # Run tests (Vitest)
pnpm lint # TypeScript check (noEmit)License
MIT
