Embarking on a new project with NestJS? This tutorial will guide you through structuring your first application effectively using modern TypeScript features.
Setting Up Your Environment
Before diving into the structure, ensure you have Node.js installed. Then, install the Nest CLI globally using npm:
npm i -g @nestjs/cli
Create a new project:
nest new your-app-name
Understanding NestJS Modules
NestJS applications are modular, with the Module
being the core building block. Here’s how to define a basic module:
import { Module } from '@nestjs/common';
@Module({
// module configuration
})
export class AppModule {}
Building Blocks of NestJS
Let’s explore the foundational elements: Controllers, Providers, and Services.
Controllers
import { Controller, Get } from '@nestjs/common';
@Controller('api/items')
export class ItemsController {
@Get()
findAll(): string {
return 'This action returns all items';
}
}
Providers and Services
Services handle business logic and are defined as providers.
import { Injectable } from '@nestjs/common';
@Injectable()
export class ItemsService {
findAll(): string[] {
return ['Item 1', 'Item 2'];
}
}
Include them in your module:
@Module({
controllers: [ItemsController],
providers: [ItemsService],
})
Scalable File Structure
A scalable NestJS file structure organizes code by feature:
src
├── items
│ ├── dto
│ ├── entities
│ ├── items.controller.ts
│ ├── items.module.ts
│ └── items.service.ts
├── users
│ ├── dto
│ ├── entities
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
├── app.module.ts
└── main.ts
Advanced Patterns
As your application grows, consider patterns like interceptors, guards, and pipelines.
Using Interceptors
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
return next.handle().pipe(
map(data => ({ data }))
);
}
}
Implementing Guards
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Add your authentication logic here
return true;
}
}
Pipelines and Custom Decorators
Using Pipes for Validation and Transformation
NestJS pipes are powerful tools for handling validation and data transformation. They can be used for tasks like validating request data or transforming input data to the desired form before it reaches the route handler.
Here’s an example of using a built-in validation pipe:
import { Controller, Get, Query, UsePipes, ValidationPipe } from '@nestjs/common';
@Controller('api/items')
export class ItemsController {
@Get()
@UsePipes(new ValidationPipe())
findItem(@Query('id') id: number): string {
return `This action returns item #${id}`;
}
}
In this example, the ValidationPipe
ensures that the id
query parameter is of the expected type.
Creating Custom Decorators
Custom decorators in NestJS allow you to abstract common logic and reuse it across your application. For instance, you can create a decorator to extract and validate user information from a request.
Here’s an example of a custom decorator that retrieves a user’s role:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUserRole = createParamDecorator(
(data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest();
return request.user.role; // Assuming 'user' and 'role' are defined in the request
}
);
You can then use this decorator in a controller:
import { Controller, Get } from '@nestjs/common';
import { GetUserRole } from './get-user-role.decorator';
@Controller('api/users')
export class UsersController {
@Get()
findUserRole(@GetUserRole() role: string): string {
return `User role: ${role}`;
}
}
Conclusion
Structuring your NestJS application with clear, modular components and leveraging advanced features ensures scalability and maintainability. Start with a solid foundation and build incrementally as your needs evolve.