πŸ“‘Building RESTful APIs

Create clean, well-structured REST APIs using NestJS conventions and best practices.

🧭 Controllers & Routing

Welcome to Controllers & Routing in NestJS! This chapter will teach you how to design and implement RESTful APIs using NestJS's powerful routing system. You'll learn about route handlers, parameter decorators, and best practices for building scalable APIs.

  • Understand the role of controllers in NestJS
  • Learn how to use route decorators like @Get, @Post, etc.
  • Discover how to capture data using parameter decorators (@Param, @Query, @Body)
  • Implement both static routes and dynamic routes
  • Handle real-world scenarios with error handling and validation

πŸ’‘ Foundational Concepts

A controller in NestJS is a class that contains methods (called handlers) which handle HTTP requests. These handlers are mapped to specific routes using decorators like @Get, @Post, etc.

import { Controller, Get } from '@nestjs/common';

@Controller('tasks')
export class TasksController {
  @Get()
  findAll() {
    return ['Task 1', 'Task 2'];
  }
}

βœ… Route Decorators

  • @Get: Handles GET requests to retrieve data
  • @Post: Handles POST requests to create new resources
  • @Put: Handles PUT requests to update existing resources
  • @Patch: Handles PATCH requests for partial updates
  • @Delete: Handles DELETE requests to remove resources

βœ… Parameter Decorators

  • @Param: Extracts route parameters
  • @Query: Extracts query string parameters
  • @Body: Extracts request body data
  • @Req: Accesses the full request object
  • @Res: Accesses the response object
@Get(':id')
findOne(@Param('id') id: string) {
  return `This action returns a #${id} task`;
}

βœ… Best Practices

  • Use meaningful route names that reflect your API's resources
  • Keep routes consistent and predictable
  • Use RESTful verbs appropriately
  • Document your endpoints using Swagger OpenAPI
  • Handle errors gracefully with proper error handling

❌ Common Mistakes to Avoid

  • Avoid overloading a single route handler with multiple responsibilities
  • Don't use too many nested routes - keep them flat where possible
  • Avoid returning raw database entities directly to the client
  • Don't forget to validate input data before processing it

πŸ’‘ Real-World Example

import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  findAll() {
    return ['User 1', 'User 2'];
  }

  @Post()
  create(@Body() user: CreateUserDto) {
    return { message: 'User created successfully' };
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return `Profile of user #${id}`;
  }
}

🧰 Services & Dependency Injection

Welcome to the chapter on Services & Dependency Injection in NestJS! This is a crucial part of building scalable and maintainable applications. You'll learn how to encapsulate business logic, manage dependencies effectively, and follow best practices for clean architecture.

πŸ’‘ Why Services Matter

In NestJS, services are the workhorses of your application. They handle all the business logic and domain operations, keeping your controllers lean and focused on handling HTTP requests.

  • Services encapsulate core business logic
  • Promote code reuse across different parts of the app
  • Support proper separation of concerns
  • Enable easier testing and maintenance

βœ… Defining Services with @Injectable

To create a service in NestJS, you use the @Injectable() decorator. This marks the class as a service that can be injected into other components.

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  // Business logic methods go here
}

βœ… Constructor Injection in Action

Dependency injection (DI) allows you to inject dependencies into your services through the constructor. This makes your code more modular and easier to test.

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor(private readonly repository: UserRepository) {}

  findAll(): User[] {
    return this.repository.findAll();
  }
}

πŸ’‘ Best Practices for Services

  • Keep controllersθ–„ and services thick
  • Avoid direct service-to-service communication without abstraction
  • Use dependency injection instead of manual object creation
  • Test your services independently
  • Document all public methods with clear JSDoc comments

❌ Common Pitfalls to Avoid

  • Don't put business logic in controllers
  • Avoid creating services that do too much (single responsibility principle)
  • Don't use global state within services
  • Avoid circular dependencies between services

πŸ’‘ Real-World Application Example

Imagine a user management system where users can be created, read, updated, and deleted. The service layer would handle all the data manipulation logic while the controller simply passes the request to the service.

// Controller
@app.post('/users')
async createUser(@body() userDto: CreateUserDto) {
  return this.userService.create(userDto);
}

// Service
@Injectable()
export class UserService {
  constructor(private userRepository: UserRepository) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const existingUser = await this.userRepository.findByEmail(createUserDto.email);
    if (existingUser) throw new ConflictError('User already exists');

    return this.userRepository.create(createUserDto);
  }
}

Quiz

Question 1 of 9

Which route decorator is used to handle GET requests for retrieving a single resource?

  • @Post
  • @Get
  • @Put
  • @Delete