0.1.3 • Published 7 months ago

@denis_bruns/nestjs-route-handler v0.1.3

Weekly downloads
-
License
MIT
Repository
-
Last release
7 months ago

@denis_bruns/nestjs-route-handler

A flexible NestJS route handler builder that offers JSON Schema validation, async/Observable flows, and various configurations.

NPM Version TypeScript License: MIT GitHub


Overview

@denis_bruns/nestjs-route-handler brings together NestJS, RxJS, and AJV-based JSON Schema validation to simplify server request handling. It helps you:

  • Validate request bodies against JSON Schemas at runtime
  • Reflect initial body or query parameters using @denis_bruns/reflection
  • Build async or Observable-based flows using your own “use cases” or inline handler functions
  • Generate consistent CORS and security headers out of the box
  • Define custom error mappings (HTTP status codes for particular error classes)
  • Enforce a payload size limit and optional request timeout to guard against resource hogs

This library is particularly useful in a clean architecture or onion architecture context, where you separate concerns into use cases or interactors that run within each request.


Key Features

  1. Inline “Use Case” Functions

    • Provide a function that returns an Observable or Promise to handle the request logic.
    • Chain multiple handlers in sequence to build complex flows.
  2. JSON Schema Validation

    • Validate request bodies using ajv for robust error reporting.
    • Provide your schema in the route config (bodySchema).
  3. Reflectors for Body and Query

    • Use reflect from @denis_bruns/reflection to transform request data or extract partial info from nested structures.
  4. CORS & Security Headers

    • Built-in CORS origin whitelisting.
    • Default security headers like Strict-Transport-Security, X-Frame-Options, etc.
  5. Timeout & Payload Size Limits

    • timeoutMs option triggers RequestTimeoutError if the use case doesn’t resolve in time.
    • maxResponseSize ensures final JSON response isn’t too large, returning PayloadTooLargeError if exceeded.
  6. Custom Error-to-Status Mappings

    • Map your custom or built-in error classes to specific HTTP status codes.
    • Example: CustomNotFoundError -> 404, CustomAuthError -> 401, etc.

Installation

With npm:

npm install @denis_bruns/nestjs-route-handler

Or with yarn:

yarn add @denis_bruns/nestjs-route-handler

You also need NestJS and Express (or a Nest platform), plus any optional libraries you want:

npm install ajv ajv-formats ajv-errors rxjs express

Basic Usage

Below is a simple NestJS controller example using nestJsRouteHandlerBuilder:

// user.controller.ts
import { Controller, Post, Req, Res, Next } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { nestJsRouteHandlerBuilder } from '@denis_bruns/nestjs-route-handler';
import { IUseCaseInlineFunc, IJsonSchema } from '@denis_bruns/core';

interface UserDTO {
  email: string;
  name: string;
  password: string;
}
interface CreatedUser {
  id: string;
  name: string;
}

// 1) Define your AJV JSON schema
const userSchema: IJsonSchema = {
  type: 'object',
  properties: {
    email: { type: 'string', format: 'email' },
    name: { type: 'string', minLength: 2 },
    password: { type: 'string', minLength: 8 }
  },
  required: ['email', 'name', 'password'],
  additionalProperties: false
};

// 2) Example "use case" inline function
const createUserUseCase: IUseCaseInlineFunc<UserDTO, UserDTO, CreatedUser> = (query) => ({
  execute: () => {
    // your create user logic here...
    return Promise.resolve({ id: '123', name: query.data?.name! });
  }
});

@Controller('user')
export class UserController {
  // 3) Build your route handler
  private readonly createUserHandler = nestJsRouteHandlerBuilder<
    UserDTO, // The shape for the initial query
    [typeof createUserUseCase] // A tuple of inline functions
  >({
    // A "reflector" describing how to pick up data from the request body
    initialQueryReflector: {
      data: {
        email: "$['body']['email']",
        name: "$['body']['name']",
        password: "$['body']['password']"
      }
    },
    handlers: [createUserUseCase],
    bodySchema: userSchema, // JSON Schema for request body
    timeoutMs: 3000, // optional, throws RequestTimeoutError if over 3s
    errorToStatusCodeMapping: {
      400: [], // schema errors default to 400
      404: [], 
      // ... custom errors
    }
  }, {
    // Handler options
    corsOriginWhitelist: ['https://mydomain.com'], // or undefined for no restriction
    maxResponseSize: 3 * 1024 * 1024, // 3 MB
    headers: {
      // override default security headers
      'X-Custom-Header': 'HelloWorld'
    }
  });

  @Post()
  async createUser(@Req() req: Request, @Res() res: Response, @Next() next: NextFunction) {
    // 4) Just call the built handler (Express-compatible)
    return await this.createUserHandler(req, res, next);
  }
}

Explanation

  1. bodySchemaajv uses this to validate the request body. If validation fails, an error with validationErrors is returned.
  2. initialQueryReflector – This uses @denis_bruns/reflection to parse the relevant fields out of req.body (or path/query parameters).
  3. handlers – The inline function(s) that will be executed in order, each returning an Observable or Promise.
  4. timeoutMs & maxResponseSize – Protect your service from slow or large payloads.
  5. CORS – Provide a corsOriginWhitelist array to allow specific origins or default to *.

Custom Error Mappings

If your “use case” throws custom errors, you can map them to specific status codes:

class MyCustomError extends Error {}

const myUseCase: IUseCaseInlineFunc<UserDTO, UserDTO, CreatedUser> = (query) => ({
  execute: () => {
    if (!query.data?.email?.endsWith('@allowed.com')) {
      throw new MyCustomError('Only allowed.com domain is permitted.');
    }
    return Promise.resolve({ id: '777', name: query.data.name! });
  }
});

const handler = nestJsRouteHandlerBuilder<UserDTO, [typeof myUseCase]>({
  initialQueryReflector: {/* ... */},
  handlers: [myUseCase],
  errorToStatusCodeMapping: {
    418: [MyCustomError], // Return 418 for MyCustomError
  }
});

Related Packages

  • @denis_bruns/core
    NPM
    GitHub
    Core types like IUseCaseInlineFunc, IQueryType, IJsonSchema, and essential error classes.

  • @denis_bruns/reflection
    NPM
    GitHub
    Used for the “reflector” mechanism to extract/transform request data via JSONPath or functions.


Contributing

Questions, issues, or improvements? Feel free to open a pull request or file an issue on GitHub.


License

This project is MIT licensed.