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.ts | A basic controller with a single route. |
app.controller.spec.ts | The unit tests for the controller. |
app.module.ts | The root module of the application. |
app.service.ts | A basic service with a single method. |
main.ts | The 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.