NestJS HTTP utilities: Resource mapping, automatic response serialization via interceptor, Swagger helpers, and paginated query/response types.
npm install @sapientpro/nestjs-httpNestJS 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)
``sh`
npm install @sapientpro/nestjs-http
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.
1) Register providers and the interceptor
If you don't use the provided module, add the HttpModule to your AppModule:
`ts
// app.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@sapientpro/nestjs-http';
@Module({
imports: [HttpModule],
})
export class AppModule {}
`
2) Create a Resource and define mapping
`ts
// 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
}
`
3) Use the Resource in a controller
`ts
// 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);
}
}
`
- 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)
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:
`ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';
// Suppose these resources exist
class ProfileResource extends Resource
class MembershipResource extends Resource
class PhoneNumberResource extends Resource
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
@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.
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.
`ts
import { ApiProperty } from '@nestjs/swagger';
import { Resource } from '@sapientpro/nestjs-http';
type DataType = { id: number; name: number };
class A extends Resource
@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.B
- An instance of will include both id and name.
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).
`ts
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
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
@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.map
- Returned fields from 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.).
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.
`ts
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
@ApiPropertyOptional()
bio?: string;
@ApiPropertyOptional()
website?: string;
}
export class UserResource extends Resource
@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.ProfileResource
- Single nested resource is auto-instantiated because the field type is .
`ts
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
@ApiProperty()
sku!: string;
@ApiProperty()
quantity!: number;
}
export class OrderResource extends Resource
@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.
`ts
import { ApiProperty } from '@nestjs/swagger';
import { Resource, ResourceMap } from '@sapientpro/nestjs-http';
type Product = {
id: number;
title: string; // we want to expose as nameprice
priceCents: number; // we want to expose as in dollars
};
@ResourceMap
map(value) {
return {
name: value.title, // rename
price: value.priceCents / 100, // compute
};
},
})
export class ProductResource extends Resource
@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.