NestJS
- Overview
- Installation
- Project Structure
- Bootstrap
- Module, Service & Controller
- Exception Filter
- Validation Pipe
Overview
NestJS uses a Dependency Injection container that initialises providers lazily. To guarantee Orion is bootstrapped before any provider accesses a model, import database.ts at the top of AppModule — outside the class body.
Installation
bash
npm install @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
npm install -D @nestjs/cli ts-node typescriptProject Structure
src/
database.ts ← Orion bootstrap
main.ts ← NestJS entry point
app.module.ts
users/
users.module.ts
users.controller.ts
users.service.ts
dto/
create-user.dto.ts
update-user.dto.ts
database/
models/
User.ts
migrations/Bootstrap
ts
// src/database.ts
import { createConnection } from '@wrsouza/orion';
export default createConnection({
connection: process.env.DATABASE_URL ?? {
driver: 'postgres',
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT ?? 5432),
database: process.env.DB_NAME ?? 'myapp',
user: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASS ?? '',
ssl: process.env.DB_SSL === 'true',
},
migrations: { path: './src/database/migrations' },
preventLazyLoading: process.env.NODE_ENV !== 'production',
});Import at the very top of AppModule — the side-effect import runs before any provider initialises:
ts
// src/app.module.ts
import '../database'; // ← bootstraps Orion before any provider runs
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({ imports: [UsersModule] })
export class AppModule {}ts
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();Module, Service & Controller
ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { ModelNotFoundException } from '@wrsouza/orion';
import { User } from '../../database/models/User';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Injectable()
export class UsersService {
async findAll(page = 1, perPage = 15) {
return User.paginate(perPage, page);
}
async findOne(id: string) {
try {
return await User.findOrFail(id);
} catch (e) {
if (e instanceof ModelNotFoundException) throw new NotFoundException();
throw e;
}
}
create(dto: CreateUserDto) {
return User.create(dto);
}
async update(id: string, dto: UpdateUserDto) {
const user = await this.findOne(id);
await user.update(dto);
return user;
}
async remove(id: string) {
const user = await this.findOne(id);
await user.delete();
}
}ts
import {
Controller, Get, Post, Put, Delete,
Param, Body, Query, HttpCode,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(@Query('page') page = 1, @Query('perPage') perPage = 15) {
return this.usersService.findAll(Number(page), Number(perPage));
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@Put(':id')
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
return this.usersService.update(id, dto);
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
}ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(1)
name!: string;
@IsEmail()
email!: string;
@IsString()
@MinLength(8)
password!: string;
}ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}Exception Filter
Use a single global filter to map all Orion exceptions to HTTP responses. See Error Handling for full details on each exception.
ts
// src/filters/orion-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
} from '@nestjs/common';
import {
ModelNotFoundException,
MassAssignmentException,
QueryException,
} from '@wrsouza/orion';
import { Response } from 'express';
@Catch(ModelNotFoundException, MassAssignmentException, QueryException)
export class OrionExceptionFilter implements ExceptionFilter {
catch(
exception: ModelNotFoundException | MassAssignmentException | QueryException,
host: ArgumentsHost,
) {
const res = host.switchToHttp().getResponse<Response>();
if (exception instanceof ModelNotFoundException) {
return res.status(HttpStatus.NOT_FOUND).json({ error: 'Not found' });
}
if (exception instanceof MassAssignmentException) {
return res
.status(HttpStatus.UNPROCESSABLE_ENTITY)
.json({ error: exception.message });
}
if (exception instanceof QueryException) {
const isUnique = (exception.cause as any).code === '23505';
if (isUnique) {
return res.status(HttpStatus.CONFLICT).json({ error: 'Already exists' });
}
return res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ error: 'Database error' });
}
}
}Register globally in main.ts:
ts
app.useGlobalFilters(new OrionExceptionFilter());Validation Pipe
ValidationPipe with whitelist: true strips any property not declared in the DTO before it reaches the service — a clean way to prevent mass-assignment without extra code.
ts
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip unknown properties
forbidNonWhitelisted: true, // throw 400 on unknown properties
transform: true, // cast query params to declared types
}));