nestjs-rest-kit
v0.0.3
Published
A collection of decorators, filters, interceptors and utilities for building REST APIs with NestJS
Maintainers
Readme
nestjs-rest-kit
A collection of decorators, filters, interceptors, and utilities for building REST APIs with NestJS.
Installation
npm install nestjs-rest-kitRequirements
- Node.js >= 18.0.0
- NestJS 10.x or 11.x
Features
- Decorators -
@RouteHandler,@RouteSuccessResponse,@PublicRoute,@Roles,@Permission - Guards -
RolesGuard,PermissionGuardfor RBAC - Filters - Standardized error response formatting
- Interceptors - Response wrapping and error handling
- Exceptions -
RestExceptionfor unified error handling - JWT - JWT authentication strategy and guards
Quick Start
Basic Setup
Register HttpExceptionFilter globally in your main.ts:
// main.ts
import { NestFactory } from '@nestjs/core';
import { HttpExceptionFilter } from 'nestjs-rest-kit';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();Note: When using
@RouteHandlerdecorator, interceptors (HttpResponseInterceptor,HttpErrorsInterceptor) are applied automatically per-route. Global filter handles exceptions that occur outside of route handlers.
API Reference
Decorators
@RouteHandler(path?, method?)
Combines HTTP method decorator with response/error interceptors and Swagger documentation.
import { Controller, Body, Param, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { RouteHandler, RouteSuccessResponse, PublicRoute } from 'nestjs-rest-kit';
@ApiTags('Users')
@Controller('users')
export class UsersController {
// POST is default method, no need to specify
@RouteHandler('create')
@RouteSuccessResponse({ description: 'Create user', type: UserDto })
async create(@Body() body: CreateUserDto) {
return this.service.create(body);
}
// GET with query params
@RouteHandler('list', 'GET')
@RouteSuccessResponse({ description: 'Get all users', type: [UserDto] })
async findAll(@Query() query: QueryDto) {
return this.service.findAll(query);
}
// GET with path parameter
@RouteHandler(':id', 'GET')
@RouteSuccessResponse({ description: 'Get user by ID', type: UserDto })
async findOne(@Param('id') id: string) {
return this.service.findOne(id);
}
// Public route (no auth required)
@PublicRoute()
@RouteHandler('health', 'GET')
healthCheck() {
return { status: 'ok' };
}
}Parameters:
path- Route path (optional)method- HTTP method:'GET'|'POST'|'PUT'|'DELETE'|'PATCH'(default:'POST')
@RouteSuccessResponse(options?)
Swagger decorator that wraps response type in SuccessResponseDto.
import { RouteSuccessResponse } from 'nestjs-rest-kit';
@RouteSuccessResponse({ type: UserDto, description: 'User found' })
findOne() { ... }@PublicRoute()
Marks a route as public (no JWT authentication required). Use with JwtAuthGuard to exclude specific routes from authentication.
import { Body } from '@nestjs/common';
import { RouteHandler, RouteSuccessResponse, PublicRoute } from 'nestjs-rest-kit';
// This route is accessible without authentication
@PublicRoute()
@RouteHandler('register')
@RouteSuccessResponse({ type: UserDto })
register(@Body() dto: RegisterDto) {
return this.authService.register(dto);
}@Roles(...roles)
Restricts access to users with specific roles. Automatically applies RolesGuard.
import { Roles } from 'nestjs-rest-kit';
// Require any of these roles (OR logic)
@Roles('admin', 'moderator')
@RouteHandler('users', 'DELETE')
deleteUser() { ... }Role checking logic:
- If
super: truein JWT → access granted (bypass) - If
role(string) in JWT → strict match against required roles - If
roles(array) in JWT → any match grants access
Multiple decorators use AND logic:
@Roles('admin')
@Roles('verified') // Must have admin AND verified@Permission(subject, action)
Restricts access based on permissions. Automatically applies PermissionGuard.
Permissions in JWT should be in subject:action format. Supports wildcards.
import { Permission } from 'nestjs-rest-kit';
// Require single permission
@Permission('users', 'delete')
@RouteHandler('users/:id', 'DELETE')
deleteUser() { ... }
// Require any of these actions (OR logic)
@Permission('users', ['read', 'write'])
@RouteHandler('users', 'GET')
getUsers() { ... }Wildcards:
// JWT: { permissions: ['admin:*'] }
// Matches: admin:read, admin:write, admin:delete, etc.Multiple decorators use AND logic:
@Permission('users', 'read')
@Permission('audit', 'read') // Must have BOTH permissions@AnyPermission(...permissions)
Allows access if user has ANY of the specified permissions (OR logic between different subjects).
import { AnyPermission } from 'nestjs-rest-kit';
// User needs 'users:delete' OR 'posts:delete'
@AnyPermission(['users', 'delete'], ['posts', 'delete'])
@RouteHandler('content/:id', 'DELETE')
deleteContent() { ... }Guards
RolesGuard
Checks user roles against @Roles decorator requirements. Applied automatically by @Roles.
PermissionGuard
Checks user permissions against @Permission and @AnyPermission decorator requirements. Applied automatically.
Interceptors
HttpResponseInterceptor
Wraps successful responses in a standardized format:
{
"success": true,
"response": { ... },
"timestamp": 1705931234567
}HttpErrorsInterceptor
Handles errors and wraps them in a standardized format. Logs 5xx errors and hides internal details.
{
"success": false,
"error": {
"statusCode": 400,
"message": "Validation failed",
"errorCode": "VALIDATION_ERROR",
"errors": {
"email": ["must be a valid email"]
}
},
"timestamp": 1705931234567
}Security: For 5xx errors, the message is replaced with "Internal server error" and errors field is omitted to prevent leaking internal details.
Filters
HttpExceptionFilter
Catches HttpException and formats the response using ErrorResponseDto.
// main.ts
import { NestFactory } from '@nestjs/core';
import { HttpExceptionFilter } from 'nestjs-rest-kit';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}Exceptions
RestException
Universal exception class that normalizes any error input into a standardized format.
import { RestException } from 'nestjs-rest-kit';
// From string
throw new RestException('Something went wrong');
// From object
throw new RestException({
message: 'User not found',
statusCode: 404,
errorCode: 'USER_NOT_FOUND',
});
// From HttpException (import { BadRequestException } from '@nestjs/common')
throw new RestException(new BadRequestException('Invalid input'));
// With validation errors
throw new RestException({
message: 'Validation failed',
statusCode: 400,
errorCode: 'VALIDATION_ERROR',
errors: {
email: ['Invalid email format'],
password: ['Too short'],
},
});DTOs
SuccessResponseDto<T>
class SuccessResponseDto<T> {
success: boolean; // always true
response?: T; // response payload
timestamp: number; // Unix timestamp in ms
}ErrorResponseDto
class ErrorResponseDto {
success: boolean; // always false
error: ErrorDetailsDto; // error details
timestamp: number; // Unix timestamp in ms
}ErrorDetailsDto
class ErrorDetailsDto {
message: string; // Error message
statusCode?: HttpStatus; // HTTP status code
errorCode?: string; // Application-specific error code
errors?: ValidationErrorsDto; // Validation errors by field
}ValidationErrorsDto
class ValidationErrorsDto {
[key: string]: string[] | ValidationErrorsDto; // Nested structure supported
}JWT Authentication
Setup
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { PassportModule } from '@nestjs/passport';
import { createJwtStrategyProvider, JwtAuthGuard } from 'nestjs-rest-kit';
@Module({
imports: [PassportModule],
providers: [
createJwtStrategyProvider(process.env.JWT_SECRET),
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AuthModule {}JwtAuthGuard
Global guard that protects all routes by default. Use @PublicRoute() to exclude specific routes.
import { Controller, Body } from '@nestjs/common';
import { RouteHandler, RouteSuccessResponse, PublicRoute } from 'nestjs-rest-kit';
@Controller('auth')
export class AuthController {
// Protected: requires valid JWT token
@RouteHandler('profile', 'GET')
@RouteSuccessResponse({ type: ProfileDto })
getProfile() {
return this.authService.getProfile();
}
// Public: accessible without authentication
@PublicRoute()
@RouteHandler('login')
@RouteSuccessResponse({ type: TokenDto })
login(@Body() dto: LoginDto) {
return this.authService.login(dto);
}
}createJwtStrategyProvider(secret)
Factory function to create JWT strategy provider.
import { createJwtStrategyProvider } from 'nestjs-rest-kit';
// In your module providers:
createJwtStrategyProvider('your-jwt-secret')Interfaces
JwtPayloadInterface
interface JwtPayloadInterface {
iss: string;
sub: string;
iat?: number;
exp?: number;
user: string;
appId?: string;
// Role-based access control
role?: string; // Single role (strict match, takes precedence)
roles?: string[]; // Multiple roles (any match)
// Permission-based access control
permissions?: string[]; // Format: 'subject:action', supports wildcards ('admin:*')
// Super user bypass
super?: boolean; // Bypasses all role and permission checks
}Full Example
// main.ts
import { NestFactory } from '@nestjs/core';
import { HttpExceptionFilter } from 'nestjs-rest-kit';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { PassportModule } from '@nestjs/passport';
import { createJwtStrategyProvider, JwtAuthGuard } from 'nestjs-rest-kit';
import { UsersModule } from './users/users.module';
@Module({
imports: [PassportModule, UsersModule],
providers: [
createJwtStrategyProvider(process.env.JWT_SECRET),
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
// users.controller.ts
import { Controller, Body, Param, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
RouteHandler,
RouteSuccessResponse,
PublicRoute,
Roles,
Permission,
RestException,
} from 'nestjs-rest-kit';
@ApiTags('Users')
@Controller('users')
export class UsersController {
// Protected route (requires authentication)
@Permission('users', 'read')
@RouteHandler('list', 'GET')
@RouteSuccessResponse({ description: 'Get all users', type: [UserDto] })
async findAll(@Query() query: QueryDto) {
return this.usersService.findAll(query);
}
// Protected route with path parameter
@Permission('users', 'read')
@RouteHandler(':id', 'GET')
@RouteSuccessResponse({ description: 'Get user by ID', type: UserDto })
async findOne(@Param('id') id: string) {
const user = await this.usersService.findOne(id);
if (!user) {
throw new RestException({
message: 'User not found',
statusCode: 404,
errorCode: 'USER_NOT_FOUND',
});
}
return user;
}
// Admin only route
@Roles('admin')
@Permission('users', 'delete')
@RouteHandler(':id', 'DELETE')
@RouteSuccessResponse({ description: 'Delete user' })
async delete(@Param('id') id: string) {
return this.usersService.delete(id);
}
// Public route (no auth required)
@PublicRoute()
@RouteHandler('register')
@RouteSuccessResponse({ description: 'Register new user', type: UserDto })
async register(@Body() dto: CreateUserDto) {
return this.usersService.register(dto);
}
}License
MIT
