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

@ismael1361/router

v2.0.44

Published

A API of router functions built with TypeScript

Downloads

1,385

Readme

@ismael1361/router

npm version License TypeScript

Módulo para Express.js com tipagem encadeada forte para body, params, query e propriedades customizadas de Request/Response. Gera automaticamente documentação OpenAPI 3.0 (Swagger UI, ReDoc e Markdown), code snippets multi-linguagem e oferece sistema de logging por stacks integrado.

📋 Índice

✨ Características

  • Tipagem Forte Encadeada — tipos de Request e Response acumulados automaticamente a cada .handler() encadeado via JoinRequest/JoinResponse
  • Inferência de Parâmetros de Rotareq.params tipado diretamente a partir da string de rota (ex.: "/users/:id"req.params.id: string)
  • Documentação OpenAPI 3.0 Automática — geração de spec, Swagger UI, ReDoc e Markdown a partir de .doc() em handlers, middlewares e rotas
  • Code Snippets — geração automática de exemplos de código em 20+ linguagens (cURL, Python, Node.js, C#, Go, etc.)
  • Middlewares Tipados com .doc() — middlewares criados via middleware() carregam documentação OpenAPI que é mesclada automaticamente na spec
  • Sub-routers Modularesrouter() e .route() permitem compor APIs em módulos independentes com prefixos
  • Tratamento de Erros CentralizadoHandleError com código HTTP, nível de log e integração automática com o sistema de stacks
  • Sistema de Stacks — logging automatizado de erros, warnings e informações em arquivo, com UI HTML acessível por rota
  • CORS Configurável — middleware Middlewares.cors() com controle granular de origens, métodos, headers e credenciais
  • Upload de Arquivos — middleware Middlewares.files() com parsing de multipart/form-data e filtragem por MIME type
  • Execução Única de Middlewarereq.executeOnce() impede re-execução de middlewares em rotas aninhadas

📦 Instalação

npm install @ismael1361/router
yarn add @ismael1361/router

🚀 Início Rápido

import { create, router, middleware, Middlewares, Request } from "@ismael1361/router";

const app = create();

// Middlewares globais
app.use(Middlewares.json());
app.use(Middlewares.cors({ allowOrigin: "*" }));

// Rota simples com inferência de parâmetros
app.get("/users/:id")
  .handler((req, res) => {
    // req.params.id é automaticamente inferido como string
    res.json({ id: req.params.id, name: "John Doe" });
  })
  .doc({
    tags: ["Users"],
    summary: "Obter usuário por ID",
    parameters: [
      { name: "id", in: "path", required: true, schema: { type: "string" } },
    ],
    responses: {
      200: { description: "Usuário encontrado" },
      404: { description: "Usuário não encontrado" },
    },
  });

// Documentação Swagger
app.defineSwagger({
  openapi: "3.0.0",
  info: { title: "Minha API", version: "1.0.0" },
});

app.listen(3000, () => {
  console.log("Servidor: http://localhost:3000");
  console.log("Swagger:  http://localhost:3000/doc/swagger");
  console.log("ReDoc:    http://localhost:3000/doc/redoc");
});

📖 API

create()

Cria uma instância de IApplication, que encapsula uma aplicação Express com roteamento tipado, documentação OpenAPI e sistema de stacks.

function create(): IApplication;

Retorno: IApplication — herda todos os métodos de IRouter (.get(), .post(), .route(), .use(), .defineSwagger()) e métodos do Express (listen, enable, disable, etc.).

import { create } from "@ismael1361/router";

const app = create();

app.get("/hello/:name")
  .handler((req, res) => {
    res.send(`Hello, ${req.params.name}!`);
  });

app.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

router()

Cria uma instância independente de IRouter para modularizar a API. Pode ser montado em outro router ou aplicação via .route() ou .use().

function router(): IRouter;
import { create, router } from "@ismael1361/router";

const app = create();

const usersRouter = router();

usersRouter.get("/")
  .handler((req, res) => {
    res.json([{ id: "1", name: "Alice" }]);
  })
  .doc({ tags: ["Users"], summary: "Listar usuários" });

usersRouter.post("/")
  .handler((req, res) => {
    res.status(201).json({ id: "2", ...req.body });
  })
  .doc({ tags: ["Users"], summary: "Criar usuário" });

// Montar com prefixo
app.route("/users", usersRouter);

handler()

Cria um handler encadeável que combina execução de middlewares com documentação OpenAPI. Cada chamada a .handler() adiciona um middleware à cadeia e mescla os tipos de Request e Response via JoinRequest/JoinResponse.

function handler<Rq extends Request, Rs extends Response>(
  fn: RequestHandler<Rq, Rs>
): IHandler<Rq, Rs>;
import { handler } from "@ismael1361/router";

const myHandler = handler((req, res, next) => {
  console.log("Primeiro middleware");
  next();
})
  .handler((req, res) => {
    res.json({ status: "ok" });
  })
  .doc({
    tags: ["Health"],
    summary: "Health check",
  });

Na maioria dos casos, handler() é usado internamente pelo router ao registrar rotas (.get(), .post(), etc.). O uso direto é raro.


middleware()

Cria um middleware reutilizável com suporte a documentação OpenAPI. Os tipos genéricos do middleware são mesclados automaticamente quando encadeado via .handler().

function middleware<Req extends Request, Res extends Response>(
  fn: RequestHandler<Req, Res>
): IMiddleware<Req, Res>;

O middleware retornado não possui o método .handler() — apenas .doc(). Ele deve ser encadeado via .handler() de um IHandler.

Middleware simples

import { middleware } from "@ismael1361/router";

const logMiddleware = middleware((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

app.get("/users")
  .handler(logMiddleware)
  .handler((req, res) => {
    res.json([]);
  });

Middleware com tipo customizado e documentação

import { middleware, Request } from "@ismael1361/router";

interface AuthRequest extends Request {
  user: { userId: string; roles: string[] };
}

const authMiddleware = middleware((req: AuthRequest, res, next) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) {
    res.status(401).json({ error: "Token ausente" });
    return;
  }
  req.user = { userId: "123", roles: ["admin"] };
  next();
}).doc({
  security: [{ bearerAuth: [] }],
  components: {
    securitySchemes: {
      bearerAuth: { type: "http", scheme: "bearer" },
    },
  },
});

// Ao encadear, req.user é inferido automaticamente
app.get("/profile")
  .handler(authMiddleware)
  .handler((req, res) => {
    // req.user.userId está disponível com tipagem
    res.json({ userId: req.user.userId });
  });

Middleware de validação de body

interface BodyRequest extends Request<string, { name: string; email: string }> {}

const validateBody = middleware((req: BodyRequest, res, next) => {
  if (!req.body.name || !req.body.email) {
    res.status(400).json({ error: "name e email são obrigatórios" });
    return;
  }
  next();
}).doc({
  requestBody: {
    required: true,
    content: {
      "application/json": {
        schema: {
          type: "object",
          required: ["name", "email"],
          properties: {
            name: { type: "string" },
            email: { type: "string", format: "email" },
          },
        },
      },
    },
  },
});

app.post("/users")
  .handler(validateBody)
  .handler((req, res) => {
    // req.body.name e req.body.email inferidos como string
    res.status(201).json({ name: req.body.name, email: req.body.email });
  });

HandleError

Classe de erro para tratamento padronizado de erros HTTP. Integra-se automaticamente com o sistema de stacks e o tratamento centralizado de erros do router.

class HandleError extends Error {
  constructor(
    message: string,
    name?: string,                   // default: "DEFAULT"
    cause?: number | Error | string, // código HTTP ou causa
    level?: "ERROR" | "WARN" | "INFO" | "NONE", // default: "ERROR"
    source?: string,
    duration?: number,
  );

  get code(): number;   // código HTTP extraído de cause
  get status(): { code: number; message: string };
  get meta(): any;
}
import { HandleError } from "@ismael1361/router";

// Erro 404
throw new HandleError("Recurso não encontrado", "NOT_FOUND", 404);

// Erro de validação (nível WARN, não polui logs de erro)
throw new HandleError("Campo email inválido", "VALIDATION_ERROR", 400, "WARN");

// Uso em handler
app.get("/users/:id")
  .handler((req, res) => {
    const user = findUser(req.params.id);
    if (!user) {
      throw new HandleError("Usuário não encontrado", "NOT_FOUND", 404);
    }
    res.json(user);
  });

O erro é capturado automaticamente pelo sistema interno (tryHandler) e retorna resposta JSON padronizada:

{ "message": "Usuário não encontrado", "name": "NOT_FOUND", "code": 404 }

Middlewares

Namespace com middlewares prontos para uso, importável via Middlewares.

import { Middlewares } from "@ismael1361/router";

Middlewares.json(options?)

Analisa corpos de requisição JSON. Wrapper de body-parser.json().

app.use(Middlewares.json());
app.use(Middlewares.json({ limit: "10mb" }));

Middlewares.raw(options?)

Analisa corpos de requisição como Buffer.

app.use(Middlewares.raw({ type: "application/octet-stream", limit: "10mb" }));

Middlewares.text(options?)

Analisa corpos de requisição como texto.

app.use(Middlewares.text({ type: "text/plain" }));

Middlewares.urlencoded(options?)

Analisa dados de formulário application/x-www-form-urlencoded.

app.use(Middlewares.urlencoded({ extended: true }));

Middlewares.cors(options?)

Configura CORS com controle granular.

// Permitir todas as origens
app.use(Middlewares.cors({ allowOrigin: "*" }));

// Origem específica com opções
app.use(Middlewares.cors({
  allowOrigin: "https://meusite.com",
  allowedMethods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  exposeHeaders: ["Content-Length"],
}));

// Sintaxe alternativa: string + objeto
app.use(Middlewares.cors("https://meusite.com", {
  allowedMethods: ["GET", "POST"],
  credentials: true,
}));

CorsOptions:

| Propriedade | Tipo | Descrição | |------------------|-----------------------------|------------------------------------------------------------------| | allowOrigin | string | Origem permitida ("*" para todas) | | allowedMethods | string[] \| string | Métodos HTTP permitidos | | allowedHeaders | string[] \| string | Headers que o cliente pode enviar | | credentials | boolean | Permite cookies e headers de autenticação | | exposeHeaders | string[] \| string | Headers adicionais legíveis pelo cliente |

Middlewares.files(...allowedMimes)

Parsing de arquivos multipart/form-data. Os arquivos ficam disponíveis em req.file (primeiro) e req.files (todos).

import { Middlewares } from "@ismael1361/router";

// Aceitar qualquer tipo de arquivo
app.post("/upload")
  .handler(Middlewares.files())
  .handler((req, res) => {
    // req.file: FileInfo (primeiro arquivo)
    // req.files: FileInfo[] (todos os arquivos)
    res.json({
      filename: req.file.originalname,
      size: req.file.size,
      mimetype: req.file.mimetype,
    });
  });

// Aceitar apenas imagens e PDFs
app.post("/upload-docs")
  .handler(Middlewares.files("image/jpeg", "image/png", "application/pdf"))
  .handler((req, res) => {
    res.json({ count: req.files.length });
  });

FileInfo:

| Propriedade | Tipo | Descrição | |----------------|------------|-----------------------------| | fieldname | string | Nome do campo do formulário | | originalname | string | Nome original do arquivo | | mimetype | string | Tipo MIME do arquivo | | size | number | Tamanho em bytes | | buffer | Buffer | Conteúdo do arquivo | | stream | Readable | Stream legível |


🔷 Interfaces e Tipos

IApplication

Estende IRouter com capacidades de servidor HTTP e sistema de stacks.

interface IApplication extends IRouter {
  listen: express.Application["listen"];
  disable: express.Application["disable"];
  enable: express.Application["enable"];
  disabled: express.Application["disabled"];
  enabled: express.Application["enabled"];
  engine: express.Application["engine"];
  param: express.Application["param"];
  render: express.Application["render"];

  getStacks(): IStackLog[];
  defineStacks(options?: IStacksOptions): { stacksPath: string };
}

IRouter

Interface principal do router. Expõe métodos HTTP, sub-rotas, middlewares e configuração Swagger.

interface IRouter extends RequestHandler {
  // Métodos HTTP — retornam IHandler com parâmetros inferidos da rota
  all:     IRouterMatcher<"all">;
  get:     IRouterMatcher<"get">;
  post:    IRouterMatcher<"post">;
  put:     IRouterMatcher<"put">;
  delete:  IRouterMatcher<"delete">;
  patch:   IRouterMatcher<"patch">;
  options: IRouterMatcher<"options">;
  head:    IRouterMatcher<"head">;

  parent: IRouter | null;
  path: string;

  param: express.Application["param"];

  route(prefix: string, doc?: MiddlewareFCDoc): IRouter;
  route(prefix: string, router: IRouter, doc?: MiddlewareFCDoc): IRouter;
  route(router: IRouter, doc?: MiddlewareFCDoc): IRouter;

  use(prefix: string, doc?: MiddlewareFCDoc): IHandler;
  use(prefix: string, handler: IRouter | RequestHandler, doc?: MiddlewareFCDoc): void;
  use(handler: IRouter | RequestHandler, doc?: MiddlewareFCDoc): void;

  defineSwagger(options: SwaggerOptions): void;
  getSwagger(): swaggerJSDoc.Options;
}

IHandler

Handler encadeável que combina execução de middlewares com documentação OpenAPI. Cada .handler() mescla tipos cumulativamente.

interface IHandler<Rq extends Request, Rs extends Response> extends RequestHandler<Rq, Rs> {
  handler<Req extends Request, Res extends Response>(
    fn: RequestHandler<Req & Rq, Res & Rs> | IHandler<Req & Rq, Res & Rs>
  ): IHandler<JoinRequest<Rq, Req>, JoinResponse<Rs, Res>>;

  doc(
    operation: MiddlewareFCDoc | swaggerJSDoc.Operation,
    components?: swaggerJSDoc.Components
  ): IHandler<Rq, Rs>;
}

Encadeamento e mesclagem de tipos:

interface AuthRequest extends Request {
  user: { id: string; roles: string[] };
}

interface PaginatedRequest extends Request<string, any, { page: number; limit: number }> {}

app.get("/items")
  .handler(authMiddleware)          // req agora tem .user
  .handler(paginationMiddleware)    // req agora tem .user + .query.page + .query.limit
  .handler((req, res) => {
    // Todos os tipos estão disponíveis simultaneamente
    console.log(req.user.id);       // string
    console.log(req.query.page);    // number
    res.json([]);
  });

IMiddleware

Middleware sem .handler(), apenas .doc(). Deve ser encadeado dentro de um IHandler.

interface IMiddleware<Rq extends Request, Rs extends Response> extends RequestHandler<Rq, Rs> {
  doc(
    operation: MiddlewareFCDoc | swaggerJSDoc.Operation,
    components?: swaggerJSDoc.Components
  ): IMiddleware<Rq, Rs>;
}

Request

Estende express.Request com parâmetros genéricos tipados.

interface Request<
  P extends string = string,      // Parâmetros de rota
  ReqBody = {},                   // Corpo da requisição
  ReqQuery = core.Query,          // Query string
  ResBody = any                   // Corpo da resposta
> extends core.Request<ParamsDictionary<P>, ResBody, ReqBody, ReqQuery> {
  clientIp?: string;
  executeOnce?: (isOnce?: boolean) => void;
}
// Parâmetros inferidos da rota
app.get("/users/:userId/posts/:postId")
  .handler((req, res) => {
    req.params.userId;  // string ✓
    req.params.postId;  // string ✓
  });

// Tipo explícito de body
interface CreateUser extends Request<string, { name: string; email: string }> {}

app.post("/users")
  .handler((req: CreateUser, res) => {
    req.body.name;   // string ✓
    req.body.email;  // string ✓
  });

Response

Estende express.Response.

interface Response<ResBody = any> extends core.Response<ResBody> {}

SwaggerOptions

Configuração para geração de documentação OpenAPI.

interface SwaggerOptions extends swaggerJSDoc.OAS3Definition {
  path?: string;                    // Prefixo das rotas de doc (default: "/doc")
  defaultResponses?: Responses;     // Respostas padrão para todas as rotas
  targets?: SnippetTargets[];       // Linguagens para code snippets
}

SnippetTargets disponíveis:

c_libcurl, csharp_restsharp, csharp_httpclient, go_native, java_okhttp, java_unirest, javascript_jquery, javascript_xhr, node_native, node_request, node_unirest, objc_nsurlsession, ocaml_cohttp, php_curl, php_http1, php_http2, python_python3, python_requests, ruby_native, shell_curl, shell_httpie, shell_wget, swift_nsurlsession


🔀 Métodos HTTP

Todos os métodos HTTP retornam um IHandler com inferência automática de parâmetros de rota.

// Assinatura
app.get<Path extends string>(path: Path, doc?: MiddlewareFCDoc): IHandler<Request<ExtractRouteParameters<Path>>>;

Métodos disponíveis: get, post, put, delete, patch, options, head, all

// GET — leitura de recursos
app.get("/status")
  .handler((req, res) => {
    res.json({ status: "ok" });
  })
  .doc({ tags: ["Health"], summary: "Status da API" });

// POST — criação de recursos
app.post("/users")
  .handler((req, res) => {
    res.status(201).json({ id: Date.now(), ...req.body });
  })
  .doc({
    tags: ["Users"],
    summary: "Criar usuário",
    requestBody: {
      required: true,
      content: {
        "application/json": {
          schema: {
            type: "object",
            required: ["name", "email"],
            properties: {
              name: { type: "string" },
              email: { type: "string", format: "email" },
            },
          },
        },
      },
    },
    responses: { 201: { description: "Usuário criado" } },
  });

// PUT — substituição completa
app.put("/users/:id")
  .handler((req, res) => {
    res.json({ id: req.params.id, ...req.body });
  })
  .doc({ tags: ["Users"], summary: "Substituir usuário" });

// PATCH — atualização parcial
app.patch("/users/:id")
  .handler((req, res) => {
    res.json({ id: req.params.id, updated: true });
  })
  .doc({ tags: ["Users"], summary: "Atualizar usuário parcialmente" });

// DELETE — remoção
app.delete("/users/:id")
  .handler((req, res) => {
    res.sendStatus(204);
  })
  .doc({ tags: ["Users"], summary: "Remover usuário" });

// ALL — responde a qualquer método
app.all("/proxy/*")
  .handler((req, res) => {
    res.json({ method: req.method, url: req.url });
  });

Documentação inline via segundo argumento:

app.post("/items", { tags: ["Items"], summary: "Criar item" })
  .handler((req, res) => {
    res.status(201).json({ id: 1 });
  });

🔗 Rotas e Sub-routers

.route() — Sub-router com prefixo

// Criar sub-router inline
const usersRoute = app.route("/users");
usersRoute.get("/").handler((req, res) => res.json([]));
usersRoute.get("/:id").handler((req, res) => res.json({ id: req.params.id }));

// Anexar router existente com documentação global
const v1 = router();

v1.get("/items")
  .handler((req, res) => res.json([]))
  .doc({ tags: ["Items"], summary: "Listar itens" });

v1.post("/items")
  .handler((req, res) => res.status(201).json(req.body))
  .doc({ tags: ["Items"], summary: "Criar item" });

// Documentação aplicada a todas as rotas do sub-router
app.route("/v1", v1, {
  security: [{ bearerAuth: [] }],
  responses: {
    400: { description: "Dados inválidos" },
    404: { description: "Não encontrado" },
  },
});

.use() — Middleware ou sub-router

// Middleware global
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// Middleware em prefixo (retorna IHandler encadeável)
app.use("/api")
  .handler((req, res, next) => {
    console.log("Requisição na /api");
    next();
  });

// Sub-router via use
const adminRouter = router();
app.use("/admin", adminRouter);

Organização modular

// routes/users.ts
import { router } from "@ismael1361/router";

export const usersRouter = router();

usersRouter.get("/")
  .handler((req, res) => res.json([]))
  .doc({ tags: ["Users"], summary: "Listar usuários" });

usersRouter.post("/")
  .handler((req, res) => res.status(201).json(req.body))
  .doc({ tags: ["Users"], summary: "Criar usuário" });

// routes/products.ts
import { router } from "@ismael1361/router";

export const productsRouter = router();

productsRouter.get("/")
  .handler((req, res) => res.json([]))
  .doc({ tags: ["Products"], summary: "Listar produtos" });

// app.ts
import { create, Middlewares } from "@ismael1361/router";
import { usersRouter } from "./routes/users";
import { productsRouter } from "./routes/products";

const app = create();
app.use(Middlewares.json());

app.route("/users", usersRouter);
app.route("/products", productsRouter);

app.defineSwagger({
  openapi: "3.0.0",
  info: { title: "API Modular", version: "1.0.0" },
});

app.listen(3000);

📚 Documentação OpenAPI/Swagger

.doc() — Documentação em handlers e middlewares

Anexa metadados OpenAPI sem alterar o fluxo de execução. Pode ser chamado em qualquer ponto da cadeia.

app.get("/users/:userId")
  .handler(authMiddleware)
  .doc({
    tags: ["Users"],
    summary: "Buscar usuário por ID",
    parameters: [
      { name: "userId", in: "path", required: true, schema: { type: "string" } },
    ],
  })
  .handler((req, res) => {
    res.json({ userId: req.params.userId });
  });

.doc() com componentes:

app.delete("/users/:id")
  .handler(authMiddleware)
  .doc(
    {
      tags: ["Users"],
      summary: "Remover usuário",
      security: [{ bearerAuth: [] }],
    },
    {
      securitySchemes: {
        bearerAuth: { type: "http", scheme: "bearer" },
      },
    },
  )
  .handler((req, res) => {
    res.sendStatus(204);
  });

.defineSwagger() — Configuração completa

Habilita a geração de documentação e cria automaticamente as seguintes rotas:

| Rota | Descrição | |-------------------------------|----------------------------------| | /doc/swagger | Interface Swagger UI | | /doc/swagger/definition.json| Spec OpenAPI JSON | | /doc/redoc | Interface ReDoc | | /doc/markdown | Documentação em Markdown | | /doc/.md | Markdown raw |

app.defineSwagger({
  openapi: "3.0.0",
  info: {
    title: "API de Exemplo",
    version: "1.0.0",
    description: "Documentação completa da API",
    contact: { name: "Suporte", email: "[email protected]" },
  },
  servers: [
    { url: "http://localhost:3000", description: "Desenvolvimento" },
    { url: "https://api.exemplo.com", description: "Produção" },
  ],
  components: {
    securitySchemes: {
      bearerAuth: {
        type: "http",
        scheme: "bearer",
        bearerFormat: "JWT",
      },
    },
  },
  // Respostas de erro aplicadas a todas as rotas
  defaultResponses: {
    400: { description: "Dados inválidos" },
    401: { description: "Falha na autenticação" },
    403: { description: "Acesso negado" },
    500: { description: "Erro interno do servidor" },
  },
  // Linguagens de code snippets (opcional)
  targets: ["shell_curl", "javascript_xhr", "node_native", "python_python3"],
});

.getSwagger() — Obter spec programaticamente

const spec = app.getSwagger();
console.log(JSON.stringify(spec.definition, null, 2));

📊 Sistema de Stacks (Logging)

Disponível apenas em IApplication (criado via create()). Registra erros, warnings e informações em arquivo de log, com UI HTML acessível por rota.

.defineStacks(options?)

interface IStacksOptions {
  path?: string;       // Rota da UI (default: "/stacks")
  limit?: number;      // Máximo de registros no arquivo (default: 100)
  filePath?: string;   // Caminho do arquivo de log (default: "./stacks.log")
  beforeStack?(...stacks: IStackLog[]): Array<IStackLog | string | Error>;
}
const { stacksPath } = app.defineStacks({
  path: "/stacks",
  limit: 200,
  filePath: "./logs/stacks.log",
  beforeStack(...stacks) {
    // Filtrar ou transformar antes de salvar
    return stacks.filter((s) => typeof s !== "string" && s.level === "ERROR");
  },
});

console.log(`Stacks UI: http://localhost:3000${stacksPath}`);

Ao ativar stacks, console.error(), console.warn() e console.info() são interceptados automaticamente e registrados no arquivo de log. Erros não capturados (unhandledRejection, uncaughtException) também são registrados.

.getStacks()

const logs: IStackLog[] = app.getStacks();
logs.forEach((log) => {
  console.log(`[${log.level}] ${log.name}: ${log.message} (${log.duration}ms)`);
});

IStackLog:

| Propriedade | Tipo | Descrição | |--------------|---------------------------------------------|--------------------------------| | time | Date | Timestamp do registro | | level | "ERROR" \| "WARN" \| "INFO" \| "DEBUG" | Nível de severidade | | name | string | Nome/categoria do log | | message | string | Mensagem descritiva | | source | string | Origem do log | | statusCode | number | Código HTTP associado | | duration | number | Duração em ms | | meta | string | Metadados adicionais |


🎯 Exemplos Avançados

Autenticação e Autorização

import { create, middleware, Middlewares, Request } from "@ismael1361/router";

interface AuthRequest extends Request {
  user: { id: string; roles: string[] };
}

const authenticate = middleware((req: AuthRequest, res, next) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) {
    res.status(401).json({ error: "Token não fornecido" });
    return;
  }
  req.user = { id: "123", roles: ["admin"] };
  next();
}).doc({
  security: [{ bearerAuth: [] }],
  components: {
    securitySchemes: {
      bearerAuth: { type: "http", scheme: "bearer" },
    },
  },
});

const authorize = (...roles: string[]) =>
  middleware((req: AuthRequest, res, next) => {
    if (!req.user.roles.some((r) => roles.includes(r))) {
      res.status(403).json({ error: "Acesso negado" });
      return;
    }
    next();
  });

const app = create();
app.use(Middlewares.json());

app.get("/admin/users")
  .handler(authenticate)
  .handler(authorize("admin"))
  .handler((req, res) => {
    // req.user está tipado
    res.json({ adminId: req.user.id, users: [] });
  })
  .doc({
    tags: ["Admin"],
    summary: "Listar usuários (Admin)",
    responses: { 200: { description: "Lista de usuários" } },
  });

Encadeamento de múltiplos handlers com tipos acumulados

interface AuthRequest extends Request {
  user: { userId: string; id: string; roles: string[] };
}

const authMiddleware = middleware((req: AuthRequest, res, next) => {
  req.user = { userId: "123", id: "abc", roles: ["admin"] };
  next();
}).doc({
  security: [{ bearerAuth: [] }],
});

app.get("/hello/:userId/:id")
  .handler(authMiddleware)        // req ganha .user
  .handler((req, res, next) => {
    // req.user e req.params.userId/id estão disponíveis
    console.log(`Usuário: ${req.user.userId}`);
    next();
  })
  .doc({
    tags: ["Users"],
    summary: "Saudação personalizada",
    parameters: [
      { name: "userId", in: "path", required: true, schema: { type: "string" } },
      { name: "id", in: "path", required: true, schema: { type: "string" } },
    ],
  })
  .handler((req, res) => {
    res.send(`Hello, ${req.params.userId}! ID: ${req.user.id}`);
  });

Execução única de middleware

const expensiveMiddleware = middleware((req, res, next) => {
  req.executeOnce?.(); // Garante uma única execução por requisição
  console.log("Operação custosa executada uma vez");
  next();
});

// Mesmo aplicado em vários níveis, executa apenas uma vez
app.use("/api")
  .handler(expensiveMiddleware)
  .handler((req, res, next) => { next(); });

app.get("/api/data")
  .handler(expensiveMiddleware) // Não executa novamente
  .handler((req, res) => {
    res.json({ data: [] });
  });

Aplicação completa com sub-routers, Swagger e stacks

import { create, router, middleware, Middlewares, Request } from "@ismael1361/router";

const app = create();

app.use(Middlewares.json());
app.use(Middlewares.cors({ allowOrigin: "*" }));

// --- Middleware de autenticação ---
interface AuthRequest extends Request<"userId" | "id"> {
  user: { userId: string; id: string; roles: string[] };
}

const authMiddleware = middleware((req: AuthRequest, res, next) => {
  const { userId = "", id = "" } = req.params;
  req.user = { userId, id, roles: ["admin"] };
  next();
}).doc({
  security: [{ bearerAuth: [] }],
  components: {
    securitySchemes: {
      bearerAuth: { type: "http", scheme: "bearer" },
    },
  },
});

// --- Rota direta na aplicação ---
app.get("/hello/:userId/:id")
  .handler(authMiddleware)
  .handler((req, res, next) => {
    console.log(`Hello, ${req.user.id}!`);
    next();
  })
  .doc({ tags: ["Users"] })
  .handler((req, res) => {
    res.send(`Hello, ${req.params.userId}! ID: ${req.user.userId}`);
  })
  .doc({
    summary: "Saudação com autenticação",
    parameters: [
      { name: "userId", in: "path", required: true, schema: { type: "string" } },
      { name: "id", in: "path", required: true, schema: { type: "string" } },
    ],
  });

// --- Sub-router v1 ---
const v1 = router();

v1.get("/test/route")
  .handler((req, res) => {
    res.send("Hello from route!");
  })
  .doc({ tags: ["V1"], summary: "Rota de teste v1" });

app.route("/v1", v1, {
  security: [{ bearerAuth: [] }],
  responses: {
    400: { description: "Account not found" },
    404: { description: "Not found" },
  },
});

// --- Swagger ---
app.defineSwagger({
  openapi: "3.0.0",
  info: { title: "My API", version: "1.0.0" },
  defaultResponses: {
    400: { description: "Dados inválidos" },
    401: { description: "Falha na autenticação" },
    403: { description: "Acesso negado" },
    500: { description: "Erro interno do servidor" },
  },
});

// --- Stacks ---
app.defineStacks({
  path: "/stacks",
  limit: 100,
  filePath: "./stacks.log",
});

app.listen(8080, () => {
  console.log("Server: http://localhost:8080");
  console.log("Swagger: http://localhost:8080/doc/swagger");
  console.log("ReDoc:   http://localhost:8080/doc/redoc");
  console.log("Stacks:  http://localhost:8080/stacks");
});

🤝 Contribuindo

  1. Faça um fork do projeto
  2. Crie uma branch para sua feature (git checkout -b feature/MinhaFeature)
  3. Commit suas mudanças (git commit -m 'Adiciona MinhaFeature')
  4. Push para a branch (git push origin feature/MinhaFeature)
  5. Abra um Pull Request

📄 Licença

Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.


Desenvolvido com ❤️ por Ismael Souza Silva