NestJS architecture
NestJS is a progressive Node.js framework for building efficient and scalable server-side applications. It adopts a modular design, combining the advantages of object-oriented programming, functional programming, and reactive programming, while defaulting to integration with Express or Fastify under the hood, offering developers flexible choices.
Core Architecture Design
The core architecture of NestJS is built upon several key concepts:
- Module System: Applications are organized into modules, with each module encapsulating specific functionality.
- Dependency Injection: Loose coupling of components is achieved through decorators.
- Controllers: Handle HTTP requests and return responses.
- Providers: Injectable components like services and repositories for business logic.
- Middleware: Functions that handle the request/response cycle.
- Exception Filters: Centralized handling of application exceptions.
- Pipes: Data validation and transformation.
- Guards: Route access control.
- Interceptors: Additional logic before and after method execution.
Module System Explained
Modules are the fundamental organizational units of NestJS applications, defined using the @Module()
decorator:
@Module({
imports: [OtherModule],
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
export class UserModule {}
imports
: Import other modules.controllers
: Register controllers.providers
: Register providers (services).exports
: Expose providers for use by other modules.
Dependency Injection Mechanism
NestJS's dependency injection system is a core strength of its architecture, implemented via constructor injection:
@Injectable()
export class UserService {
constructor(private readonly repository: UserRepository) {}
async findAll(): Promise<User[]> {
return this.repository.find();
}
}
Using services in controllers:
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async findAll(): Promise<User[]> {
return this.userService.findAll();
}
}
Controllers and Routing
Controllers handle incoming requests and return responses:
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns a #${id} cat`;
}
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
}
Providers and Services
Providers are the primary carriers of business logic in NestJS:
@Injectable()
export class NotificationService {
private readonly clients: Client[] = [];
addClient(client: Client) {
this.clients.push(client);
}
sendAll(message: string) {
this.clients.forEach(client => client.send(message));
}
}
Middleware System
Middleware can process requests and responses:
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request... ${req.method} ${req.path}`);
next();
}
}
Applying middleware in a module:
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*');
}
}
Exception Handling
NestJS provides an exception filter mechanism:
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
message: exception.message
});
}
}
Using in controllers:
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
Pipes and Data Validation
Pipes are used for data transformation and validation:
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (!value) {
throw new BadRequestException('No data submitted');
}
return value;
}
}
Using built-in validation pipes:
@Post()
async create(
@Body(new ValidationPipe()) createUserDto: CreateUserDto
) {
// ...
}
Guards and Authentication
Guards are used for route access control:
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Using in controllers:
@UseGuards(AuthGuard)
@Controller('profile')
export class ProfileController {}
Interceptors
Interceptors can add logic before and after method execution:
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
}
}
Applying interceptors:
@UseInterceptors(LoggingInterceptor)
@Controller('users')
export class UserController {}
Microservices Support
NestJS natively supports microservice architecture:
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 }
}
);
await app.listen();
WebSocket Integration
NestJS provides excellent WebSocket support:
@WebSocketGateway()
export class EventsGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('events')
handleEvent(client: any, data: string): string {
return data;
}
}
Testing Strategy
NestJS offers comprehensive testing tools:
describe('UserController', () => {
let userController: UserController;
let userService: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [UserService],
}).compile();
userService = moduleRef.get<UserService>(UserService);
userController = moduleRef.get<UserController>(UserController);
});
describe('findAll', () => {
it('should return an array of users', async () => {
const result = ['test'];
jest.spyOn(userService, 'findAll').mockImplementation(() => result);
expect(await userController.findAll()).toBe(result);
});
});
});
Configuration Management
NestJS recommends using ConfigModule for configuration management:
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.development.env',
}),
],
})
export class AppModule {}
Using configurations:
@Injectable()
export class DatabaseService {
constructor(private configService: ConfigService) {
const dbUser = this.configService.get<string>('DATABASE_USER');
}
}
Database Integration
NestJS supports various ORM integrations, with TypeORM as an example:
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'test',
entities: [User],
synchronize: true,
}),
TypeOrmModule.forFeature([User]),
],
})
export class AppModule {}
Defining entities:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
}
Performance Optimization
NestJS applications can be optimized in various ways:
- Use FastifyAdapter instead of the default Express.
- Implement caching strategies.
- Use compression middleware.
- Enable cluster mode.
- Optimize the dependency injection tree.
Enabling Fastify:
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
await app.listen(3000);
}
Deployment Strategies
NestJS applications can be deployed in various environments:
- Traditional server deployment.
- Containerized deployment (Docker).
- Serverless architecture.
- Platform as a Service (PaaS).
Docker deployment example:
FROM node:16-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main"]
Ecosystem and Plugins
NestJS has a rich ecosystem:
@nestjs/swagger
- API documentation generation.@nestjs/graphql
- GraphQL support.@nestjs/bull
- Queue processing.@nestjs/terminus
- Health checks.@nestjs/schedule
- Task scheduling.
Swagger integration example:
const config = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
Architectural Best Practices
- Design modules following the Single Responsibility Principle.
- Organize code structure using Domain-Driven Design (DDD).
- Maintain clear layer separation (controller-service-repository).
- Keep business logic decoupled from the framework.
- Write comprehensive unit and integration tests.
Domain-Driven Design example structure:
src/
├── users/
│ ├── entities/
│ ├── repositories/
│ ├── services/
│ ├── controllers/
│ └── users.module.ts
├── products/
│ ├── entities/
│ ├── repositories/
│ ├── services/
│ ├── controllers/
│ └── products.module.ts
└── shared/
├── infrastructure/
└── common/
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn