Lesson 02-NestJS Controllers and Providers

Controllers

Introduction to Controllers

Controllers are core components in NestJS responsible for handling HTTP requests. They receive requests, process business logic, and send responses to clients.

Creating a Controller
Use the Nest CLI to create a controller:

nest generate controller cats

This generates a CatsController in the src/controllers/cats.controller.ts file.

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

@Controller('example')
export class ExampleController {
  @Get()
  getHello(): string {
    return 'Hello World!';
  }
}

Decorators

Common Decorators

  • @Controller: Defines the base path for the controller.
  • @Get: Handles GET requests.
  • @Post: Handles POST requests.
  • @Put: Handles PUT requests.
  • @Delete: Handles DELETE requests.

Example Code

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

@Controller('posts')
export class PostsController {
  @Get()
  getAllPosts(): string {
    return 'Get all posts';
  }

  @Get(':id')
  getPostById(@Param('id') id: string): string {
    return `Get post with ID ${id}`;
  }

  @Post()
  createPost(@Body() body: any): string {
    return `Create post with data: ${JSON.stringify(body)}`;
  }

  @Put(':id')
  updatePost(@Param('id') id: string, @Body() body: any): string {
    return `Update post with ID ${id} and data: ${JSON.stringify(body)}`;
  }

  @Delete(':id')
  deletePost(@Param('id') id: string): string {
    return `Delete post with ID ${id}`;
  }
}

Routing

Routing maps URLs to controller methods that handle requests.

Basic Routing

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

Route Parameters

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

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }
}

Request Object

NestJS supports various request objects, such as Request, Response, and NextFunction.

Using the Request Object

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() req) {
    console.log(req.headers); // Access request headers
    return 'This action returns all cats';
  }
}

Receiving Request Parameters

NestJS supports multiple ways to receive request parameters, including path parameters, query parameters, and request bodies.

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

@Controller('posts')
export class PostsController {
  @Get(':id')
  getPostById(@Param('id') id: string): string {
    return `Get post with ID ${id}`;
  }

  @Get()
  getPosts(@Query('status') status: string): string {
    return `Get posts with status: ${status}`;
  }

  @Post()
  createPost(@Body() body: any): string {
    return `Create post with data: ${JSON.stringify(body)}`;
  }
}

Returning Responses

Controllers can return various response types, including text, JSON, or views.

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

@Controller('example')
export class ExampleController {
  @Get('text')
  getTextResponse(): string {
    return 'This is a plain text response.';
  }

  @Get('json')
  getJsonResponse(): object {
    return { message: 'Hello, World!' };
  }

  @Get('view')
  getViewResponse(@Res() res: Response): void {
    res.render('welcome', { name: 'John Doe' });
  }
}

Resources

Resources refer to data or functionality accessed via HTTP methods and paths.

RESTful Resources

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }

  @Post()
  create(@Body() createCatDto) {
    return `This action adds a new cat ${createCatDto.name}`;
  }
}

Route Wildcards

Wildcard routes match any requests that do not match other route patterns.

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

@Controller('cats')
export class CatsController {
  @All('*')
  catchAll() {
    return 'This is a catch-all route';
  }
}

Status Codes

Use the HttpStatus enum to set HTTP status codes.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Res() res) {
    res.status(HttpStatus.OK).send('This action returns all cats');
  }
}

Headers

HTTP headers can be set in responses.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Res() res) {
    res.setHeader('Content-Type', 'application/json');
    res.status(HttpStatus.OK).send('This action returns all cats');
  }
}

View Rendering

NestJS supports template engines (e.g., EJS, Handlebars) for rendering views.

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

@Controller('example')
export class ExampleController {
  @Get('view')
  getViewResponse(@Res() res: Response): void {
    res.render('welcome', { name: 'John Doe' });
  }
}

Redirection

Use the redirect method to redirect to another URL.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Res() res) {
    res.redirect(302, '/dogs');
  }
}

Route Parameters

Route parameters extract values from the URL.

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

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }
}

Subdomain Routing

Subdomain routing can be implemented through configuration.

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

@Controller('cats')
@Host('api.example.com')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

Scopes

Scopes limit the scope of a controller’s functionality.

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

@Controller('cats')
@UseGuards(AuthGuard())
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

Session Management

Controllers can manage session data, such as setting and retrieving session variables.

import { Controller, Get, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Controller('session')
export class SessionController {
  @Get('set')
  setSession(@Req() req: Request): string {
    req.session.name = 'John Doe';
    return 'Session set successfully.';
  }

  @Get('get')
  getSession(@Req() req: Request): string {
    const name = req.session.name || 'Guest';
    return `Session value: ${name}`;
  }
}

Data Validation

Controllers can validate request data.

import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiBody, ApiOperation } from '@nestjs/swagger';

@Controller('example')
export class ExampleController {
  @Post('validate')
  @UsePipes(ValidationPipe)
  @ApiOperation({ summary: 'Validate user data' })
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' },
        password: { type: 'string', minLength: 8 },
      },
    },
  })
  validateUserData(@Body() body: any): string {
    return `Data received and validated: ${JSON.stringify(body)}`;
  }
}

Asynchronicity

Controller methods can be asynchronous.

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

@Controller('cats')
export class CatsController {
  @Get()
  async findAll() {
    const cats = await this.getCatsFromDatabase();
    return cats;
  }

  private async getCatsFromDatabase() {
    // Simulate fetching data from a database
    return ['cat1', 'cat2'];
  }
}

Request Payload

Request payloads can be accessed using decorators like @Body and @Query.

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

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto) {
    return `This action adds a new cat ${createCatDto.name}`;
  }
}

Error Handling

Errors can be handled using global exception filters or within controllers.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    throw new HttpException('Failed to find cats', HttpStatus.NOT_FOUND);
  }

  @Get('error')
  throwError(): never {
    throw new HttpException('Something went wrong.', HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

RESTful Controllers

NestJS provides a convenient way to create RESTful controllers.

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

@Controller('posts')
export class PostsController {
  @Get()
  getAllPosts(): string {
    return 'Get all posts';
  }

  @Get(':id')
  getPostById(@Param('id') id: string): string {
    return `Get post with ID ${id}`;
  }

  @Post()
  createPost(@Body() body: any): string {
    return `Create post with data: ${JSON.stringify(body)}`;
  }

  @Put(':id')
  updatePost(@Param('id') id: string, @Body() body: any): string {
    return `Update post with ID ${id} and data: ${JSON.stringify(body)}`;
  }

  @Delete(':id')
  deletePost(@Param('id') id: string): string {
    return `Delete post with ID ${id}`;
  }
}

Middleware in Controllers

Controllers can use middleware for pre- or post-processing operations.

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

@Controller('example')
@UseGuards(AuthGuard('jwt'))
export class ExampleController {
  @Get()
  getHello(): string {
    return 'Welcome to the dashboard.';
  }
}

Complete Resource Example

A complete resource controller example.

import { Controller, Get, Post, Body, Param, Put, Delete, UseGuards } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';
import { AuthGuard } from '@nestjs/passport';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @UseGuards(AuthGuard())
  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }

  @UseGuards(AuthGuard())
  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @UseGuards(AuthGuard())
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.catsService.findOne(+id);
  }

  @UseGuards(AuthGuard())
  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return this.catsService.update(+id, updateCatDto);
  }

  @UseGuards(AuthGuard())
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.catsService.remove(+id);
  }
}

Starting and Running

Start the NestJS application.

npm run start:dev

Library-Specific Methods

NestJS supports various libraries and frameworks, such as Passport.js for authentication.

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

@Controller('cats')
export class CatsController {
  @Get()
  @UseGuards(AuthGuard('jwt'))
  findAll() {
    return 'This action returns all cats';
  }
}

Providers

Services and providers in NestJS v10 are critical components for encapsulating business logic and implementing dependency injection.

Services

Services are components in NestJS used to encapsulate business logic. They typically do not interact directly with HTTP requests but are called by controllers or other services.

Creating a Service
Use the Nest CLI to create a service:

nest generate service cats

This generates a CatsService in the src/services/cats.service.ts file.

Dependency Injection

Dependency injection is a design pattern used to manage dependencies between components. In NestJS, dependency injection is achieved through providers.

Creating a Service

// src/services/cats.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  create(createCatDto: any) {
    return 'This action adds a new cat';
  }

  findAll() {
    return `This action returns all cats`;
  }

  findOne(id: number) {
    return `This action returns a #${id} cat`;
  }

  update(id: number, updateCatDto: any) {
    return `This action updates a #${id} cat`;
  }

  remove(id: number) {
    return `This action removes a #${id} cat`;
  }
}

Scopes

Scopes define the lifecycle of a provider. NestJS supports three scopes: Singleton, Request, and Transient.

Singleton Scope

The default scope, where the provider is instantiated only once throughout the application’s lifecycle.

// src/services/cats.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  // ...
}

Request Scope

A new instance is created for each HTTP request.

// src/services/cats.service.ts
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class CatsService {
  // ...
}

Transient Scope

A new instance is created each time the provider is requested.

// src/services/cats.service.ts
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.TRANSIENT })
export class CatsService {
  // ...
}

Custom Providers

In addition to class providers, custom providers can be created using factory functions or value providers.

Factory Function Provider

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './controllers/cats.controller';
import { CatsService } from './services/cats.service';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [
    CatsService,
    {
      provide: 'CATS_REPOSITORY',
      useFactory: () => ({
        findAll: () => ['cat1', 'cat2'],
      }),
    },
  ],
})
export class AppModule {}

Value Provider

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './controllers/cats.controller';
import { CatsService } from './services/cats.service';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [
    CatsService,
    {
      provide: 'CATS_REPOSITORY',
      useValue: {
        findAll: () => ['cat1', 'cat2'],
      },
    },
  ],
})
export class AppModule {}

Optional Providers

In some cases, dependencies may be optional, which can be achieved using the @Optional() decorator.

// src/services/cats.service.ts
import { Injectable, Optional } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(@Optional() private readonly catsRepository?) {}

  findAll() {
    if (this.catsRepository) {
      return this.catsRepository.findAll();
    }
    return [];
  }
}

Property-Based Injection

The @Inject decorator can be used to specify dependencies.

// src/services/cats.service.ts
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(@Inject('CATS_REPOSITORY') private readonly catsRepository) {}

  findAll() {
    return this.catsRepository.findAll();
  }
}

Provider Registration

Providers must be registered in a module to be used.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './controllers/cats.controller';
import { CatsService } from './services/cats.service';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

Manual Instantiation

While NestJS typically manages dependencies automatically, you may need to manually instantiate providers in some cases.

// src/controllers/cats.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatsService } from '../services/cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    const catsService = new CatsService(); // Manual instantiation
    return catsService.findAll();
  }
}

Example Code

Service

// src/services/cats.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  create(createCatDto: any) {
    return 'This action adds a new cat';
  }

  findAll() {
    return `This action returns all cats`;
  }

  findOne(id: number) {
    return `This action returns a #${id} cat`;
  }

  update(id: number, updateCatDto: any) {
    return `This action updates a #${id} cat`;
  }

  remove(id: number) {
    return `This action removes a #${id} cat`;
  }
}

Controller

// src/controllers/cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from '../services/cats.service';
import { CreateCatDto } from '../dto/create-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }

  @Get()
  findAll() {
    return this.catsService.findAll();
  }
}

Module

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './controllers/cats.controller';
import { CatsService } from './services/cats.service';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}
Share your love