npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alis-kit/routers

v3.0.1

Published

TC39 decorator-based controller library for Node.js APIs. Supports Express and Fastify with Zod validation.

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.

npm version license

🚧 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

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 >= 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(): 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?)

⚠️ Deprecated in v3.0 — use @RestController + @Authentication + @Tag instead.

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 cookie

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