npm.io
2.1.0 • Published yesterday

@alis-kit/routers

Licence
MIT
Version
2.1.0
Deps
1
Size
166 kB
Vulns
0
Weekly
0

@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 handling
  • decorators/ — 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

Keywords