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

@sapientpro/nestjs-http

v0.0.6

Published

NestJS HTTP utilities: Resource mapping, automatic response serialization via interceptor, Swagger helpers, and paginated query/response types.

Readme

@sapientpro/nestjs-http

NPM Package

NestJS HTTP utilities for building clean, well‑documented APIs:

  • Resource class and ResourceMap decorator for mapping domain objects to API responses
  • Automatic response serialization via ResourceInterceptor and ResourcesService
  • Swagger helper for paginated responses (ApiPaginatedResponse)
  • Validated pagination query DTO (PaginatedQuery)

Installation

npm install @sapientpro/nestjs-http

What problem does it solve?

This module helps you separate your domain data from your HTTP responses. Define lightweight Resource classes that declare exactly what goes out over the wire. A global interceptor serializes your controller responses (including nested Resources, arrays, Maps/Sets, Dates, Buffers, and BigInt) into JSON‑safe structures and respects Swagger metadata for better API docs.

Features at a glance

  • Resource base class with helpers: make, collection, paginated
  • ResourceMap decorator to declare how to map raw data to response fields (with optional DI injections)
  • Global ResourceInterceptor to serialize responses consistently
  • ApiPaginatedResponse decorator to document list endpoints in Swagger
  • PaginatedQuery DTO with class‑validator rules and Swagger defaults

Quick start

  1. Register providers and the interceptor

If you don't use the provided module, add the HttpModule to your AppModule:

// app.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@sapientpro/nestjs-http';

@Module({
    imports: [HttpModule],
})
export class AppModule {}
  1. Create a Resource and define mapping
// post.resource.ts
import { ApiProperty } from '@nestjs/swagger';
import { Resource, ResourceMap } from '@sapientpro/nestjs-http';

export class PostResource extends Resource<{ id: string; title: string; content: string; createdAt: Date }> {
  @ApiProperty()
  id!: string;

  @ApiProperty()
  title!: string;

  @ApiProperty()
  content!: string;

  @ApiProperty()
  createdAt!: string; // will be ISO string after mapping
}
  1. Use the Resource in a controller
// posts.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ApiProperty } from "@nestjs/swagger";
import { ApiPaginatedResponse } from '@sapientpro/nestjs-http';
import { PaginatedQuery, Resource } from '@sapientpro/nestjs-http';

@Controller('posts')
export class PostsController {
    @Get()
    @ApiPaginatedResponse({ type: PostResource, description: 'List posts' })
    async list(@Query() query: PaginatedQuery) {
        const data = [
            { id: '1', title: 'Hello', content: '...', createdAt: new Date() },
        ];
        const total = 1;
        return PostResource.paginated(data, total);
    }
}

API

  • Resource

    • new Resource(data)
    • static make(data)
    • static collection(dataArray)
    • static paginated(dataArray, total)
  • @ResourceMap({ map, injects? })

    • map receives the original data and returns an object with public fields to expose
    • injects allows resolving providers to use inside map
  • ApiPaginatedResponse({ type, status?, omit?, extends?, description? })

    • Adds Swagger schema for { data: T[], total: number }
    • omit lets you hide some fields from the schema (writeOnly)
    • extends allows adding extra top‑level properties to the wrapper
  • PaginatedQuery

    • limit?: number (default 20, max 100)
    • offset?: number (default 0)

Serialization rules

  • BigInt -> string
  • Date -> ISO string
  • Buffer -> base64 string
  • Map -> plain object
  • Set -> array
  • Nested arrays, objects, and Resources are recursively mapped
  • Swagger @ApiProperty metadata on Resource fields is honored, including nested Resource types

Advanced mapping examples

1) Auto-expanding nested Resources from entity data

When a Resource field is typed as another Resource (optionally via Swagger's type), the mapper will automatically construct that nested Resource from the original entity value. Arrays are supported too.

Example:

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';

// Suppose these resources exist
class ProfileResource extends Resource<any> {}
class MembershipResource extends Resource<any> {}
class PhoneNumberResource extends Resource<any> {}

type AccountEntity = {
  id: string;
  name: string;
  profile?: any; // data to feed into ProfileResource
  memberships?: any[]; // data to feed into MembershipResource[]
  phoneNumbers?: any[]; // data to feed into PhoneNumberResource[]
};

export class AccountSummaryResource extends Resource<AccountEntity> {
  @ApiProperty()
  id!: string;

  @ApiProperty()
  name!: string;

  @ApiPropertyOptional()
  profile?: ProfileResource; // will be auto-mapped from entity.profile

  @ApiPropertyOptional({ type: MembershipResource, isArray: true })
  memberships?: MembershipResource[]; // auto-mapped from entity.memberships

  @ApiPropertyOptional({ type: PhoneNumberResource, isArray: true })
  phoneNumbers?: PhoneNumberResource[]; // auto-mapped from entity.phoneNumbers
}

Result: profile, memberships, and phoneNumbers will be automatically expanded from the corresponding AccountEntity fields and converted to the specified Resource types.

2) Inheritance-aware field resolution

If you extend a Resource, only the declared fields of each class are auto-resolved from the data type assigned to that class in the hierarchy.

import { ApiProperty } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';

type DataType = { id: number; name: number };

class A extends Resource<DataType> {
  @ApiProperty()
  id!: number; // A will map only `id` from DataType
}

class B extends A {
  @ApiProperty()
  name!: number; // B adds `name`, so B will map both `id` and `name`
}
  • An instance of A will include only id.
  • An instance of B will include both id and name.

3) Using ResourceMap for custom mapping (with DI)

You can override or enrich the auto-resolved properties using @ResourceMap. Inject services (including forwardRef) into the mapper and return an object with fields to set explicitly. Fields returned by map are ignored by the auto-resolver (i.e., they won't be auto-populated again).

import { forwardRef, Injectable } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { Resource, ResourceMap } from '@sapientpro/nestjs-http';

class SomeService { /* ... */ }
class Service2 { /* ... */ }

type DataType = { raw: string; computed?: string };

@ResourceMap<DataType>({
  injects: [SomeService, forwardRef(() => Service2)],
  map(value: DataType, someService: SomeService, service2: Service2) {
    return {
      // Any fields you return here will be set on the resource and excluded from auto-resolving
      computed: `${value.raw}-${someService.get('x')}`,
    };
  },
})
export class ExampleResource extends Resource<DataType> {
  @ApiProperty()
  raw!: string; // will be auto-resolved unless overridden in map

  @ApiProperty()
  computed!: string; // provided by ResourceMap.map, so auto-resolve will skip it
}

Notes:

  • The map function receives the original data and any injected services.
  • Returned fields from map are considered resolved and will not be auto-filled afterward.
  • Remaining declared properties on the Resource are auto-resolved from the source data (including nested Resources, arrays, Dates, Buffers, BigInt, Maps/Sets, etc.).

Creating new resources (cookbook)

Below are small, copy–pasteable examples you can adapt when creating new Resources. These are examples only; no source files are added to the library.

A) Simple nested resources with arrays

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';

type Profile = {
  bio?: string;
  website?: string;
};

type UserEntity = {
  id: string;
  name: string;
  tags?: string[];
  profile?: Profile;
};

export class ProfileResource extends Resource<Profile> {
  @ApiPropertyOptional()
  bio?: string;

  @ApiPropertyOptional()
  website?: string;
}

export class UserResource extends Resource<UserEntity> {
  @ApiProperty()
  id!: string;

  @ApiProperty()
  name!: string;

  @ApiPropertyOptional({ isArray: true, type: String })
  tags?: string[];

  @ApiPropertyOptional({ type: ProfileResource })
  profile?: ProfileResource; // auto-expanded from entity.profile
}
  • Arrays of primitives work with type: String | Number | Boolean.
  • Single nested resource is auto-instantiated because the field type is ProfileResource.

B) Handling Date, Buffer, BigInt and arrays of nested resources

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';

// Domain types
export type OrderItem = { sku: string; quantity: number };
export type Order = {
  id: bigint;        // will be serialized as string
  createdAt: Date;   // will be serialized as ISO string
  invoicePdf?: Buffer; // will be base64 string if present
  items: OrderItem[];
};

export class OrderItemResource extends Resource<OrderItem> {
  @ApiProperty()
  sku!: string;

  @ApiProperty()
  quantity!: number;
}

export class OrderResource extends Resource<Order> {
  @ApiProperty()
  id!: string; // BigInt -> string

  @ApiProperty()
  createdAt!: string; // Date -> ISO string

  @ApiPropertyOptional()
  invoicePdf?: string; // Buffer -> base64

  @ApiProperty({ type: OrderItemResource, isArray: true })
  items!: OrderItemResource[]; // auto-expanded
}
  • You can still declare id as bigint and createdAt as Date in the resource; the interceptor will serialize them. Typing as string in the Resource reflects the final JSON.

C) Renaming fields and computing values with ResourceMap

import { ApiProperty } from '@nestjs/swagger';
import { Resource, ResourceMap } from '@sapientpro/nestjs-http';

type Product = {
  id: number;
  title: string;           // we want to expose as `name`
  priceCents: number;      // we want to expose as `price` in dollars
};

@ResourceMap<Product>({
  map(value) {
    return {
      name: value.title,                // rename
      price: value.priceCents / 100,    // compute
    };
  },
})
export class ProductResource extends Resource<Product> {
  @ApiProperty()
  id!: number; // auto-resolved

  @ApiProperty()
  name!: string; // provided by map, excluded from auto-resolve

  @ApiProperty()
  price!: number; // provided by map
}

Tip: Any fields returned by map are considered resolved and won’t be auto-populated again.