How to Structure Your First NestJS Application

Updated: December 31, 2023 By: Guest Contributor Post a comment

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.