A Comprehensive Guide to Creating APIs in NestJS

Table of Contents:
1. What is NestJS and Why use it ?
2. Installation and Setup
3. Understanding Controllers, Services, and Modules
4. Handling Request Data
5. Error Handling
6. Implementing Middleware
7. Authentication and Authorization

What is NestJS and Why use it ?

NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It leverages TypeScript, which is a statically typed superset of JavaScript, to provide a more structured and maintainable codebase. NestJS is heavily inspired by Angular’s architecture and provides a similar modular and dependency injection system.

Advantages of using NestJS:

  • Modular Architecture: NestJS facilitates organized and scalable applications through its modular architecture, akin to Angular.
  • TypeScript Support: Benefit from enhanced code quality, type safety, and improved developer productivity with NestJS’s TypeScript integration.
  • Built-in Dependency Injection: Simplify code management and improve testability by leveraging Nest’s built-in dependency injection mechanism.
  • Developer-Friendly Syntax: Enjoy a clean and expressive syntax in NestJS, reducing boilerplate code and speeding up development cycles.
  • Powerful CLI: Automate common tasks with the Nest CLI, boosting project setup and ensuring consistency across developments.
  • Compatibility with Node.js Ecosystem: Utilize existing Node.js libraries and middleware seamlessly within NestJS, ensuring compatibility and enhancing development flexibility.

In this guide, we’ll walk you through the process of creating APIs in NestJS, covering everything from setup to implementation.

Installation and Setup

Start by installing NestJS and setting up a new project using the Nest CLI. Follow the installation process outlined in the documentation to get up and running quickly.

npm install -g @nestjs/cli
nest new my-nest-project
cd my-nest-project

The project-name directory will be created, node modules and a few other boilerplate files will be installed, and a src/ directory will be created and populated with several core files.

src ->
app.controller.spec.ts
app.controller.ts
app.module.ts
app.service.ts
main.ts

Here’s a brief overview of those core files:

app.controller.tsA basic controller with a single route.
app.controller.spec.tsThe unit tests for the controller.
app.module.tsThe root module of the application.
app.service.tsA basic service with a single method.
main.tsThe entry file of the application which uses the core function NestFactory to create a Nest application instance.

Understanding Controllers, Services, and Modules

NestJS follows a modular structure with controllers, services, and modules as its core components. Controllers handle incoming requests, services encapsulate business logic, and modules organize application components.

Nest CLI helps to create the module, service and controller files with the following commands respectively:

nest g module users

nest g service users

nest g controller users

Define your routes and handlers in your controllers to map incoming requests to specific endpoints and implement the corresponding business logic using services, as shown below.

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

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

  @Get()
  findAll(): string {
    return this.catsService.findAll();
  }
}
// cats.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  findAll(): string {
    return 'This action returns all cats';
  }
}

Handling Request Data

Explore various techniques for handling request data, including query parameters, request bodies, and route parameters. Learn how to validate and sanitize input data to ensure data integrity and security.

// users.controller.ts
import { Body, Controller, Delete, Get, Param, Post, Put, Query, ParseIntPipe, ValidationPipe } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dtos/create-user.dto';
import { UpdateUserDto } from './dtos/update-user.dto';

@Controller('users')
export class UsersController {
    constructor(private readonly userService: UsersService) {}

    @Get()
    findAllUsers(@Query('role') role: 'INTERN' | 'ENGINEER' | 'ADMIN') {
        return this.userService.findAllUsers(role);
    }

    @Get(':id')
    findOneUser(@Param('id', ParseIntPipe) id: number) {
        return this.userService.findOneUser(id);
    }

    @Post()
    createUser(@Body(ValidationPipe) user: CreateUserDto) {
        return this.userService.createUser(user);
    }

You can see the CreateUserDto and UpdateUserDto in the code above. They are called Data Transfer Objects (DTOs). In NestJS, a Data Transfer Object (DTO) is a plain TypeScript object used to transfer data between different parts of the application, typically between the client and the server.

DTOs are commonly used to define the structure of data sent over the network in API requests and responses. They can also be used for validation and data transformation.

// create-user.dto.ts
export class CreateUserDto {
    name: string;
    email: string;
    role: 'INTERN' | 'ENGINEER' | 'ADMIN';
}
// update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}

Error Handling

Discover best practices for error handling in NestJS, including centralized error handling using exception filters and custom error responses. As you can see, “NotFoundException” is an exception filter provided by NestJS to throw error when the data we require is not found, along with customised return messages.

// users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { UpdateUserDto } from './dtos/update-user.dto';
import { CreateUserDto } from './dtos/create-user.dto';

@Injectable()
export class UsersService {
private users = []; // Users data with id, name, email and role for instance

findAllUsers(role?: 'INTERN' | 'ENGINEER' | 'ADMIN') {
        if (role) {
            const users = this.users.filter(user => user.role.toLowerCase() === role.toLowerCase());
            if (!users.length) throw new NotFoundException('No user with this role found!');
            return users;
        }
        return this.users;
    }

    findOneUser(id: number) {
        const user = this.users.find(user => user.id === id);
        if (!user) throw new NotFoundException('No user found!');
        return user;
    }
}

Implementing Middleware

Understand the role of middleware in NestJS and learn how to create custom middleware to intercept and modify incoming requests and outgoing responses.

// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}
// app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

Authentication and Authorization

Implement authentication and authorization mechanisms in your APIs using NestJS middleware, decorators, and guards to control access to resources and protect sensitive data.

// auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

function validateRequest(request: any): boolean {
  // Check if request is authenticated
  return true;
}
// cats.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { CatsService } from './cats.service';
import { AuthGuard } from '../auth.guard';

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

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

In conclusion, NestJS presents a modern and efficient framework for building server-side applications in Node.js. With its modular architecture, TypeScript support, and developer-friendly syntax, NestJS streamlines development, enhances scalability, and improves maintainability. By leveraging features like built-in dependency injection and a powerful CLI, developers can create robust and expressive APIs with ease, making NestJS a compelling choice for modern web development projects.

You may also like

Leave a Reply