TypeScript NestJS
Introduction
NestJS is a powerful, progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It's built with and fully supports TypeScript, though you can also use pure JavaScript. NestJS combines elements of Object-Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP).
What makes NestJS special is its architecture heavily inspired by Angular, providing a structured way to organize your code using modules, controllers, and services. This architecture promotes code reusability, testability, and maintainability - ideal for large-scale applications.
Why NestJS?
- TypeScript-first: Built with and optimized for TypeScript
- Modular architecture: Organize your application into reusable modules
- Dependency injection: Built-in powerful DI system
- Middleware support: Similar to Express middleware
- Testing utilities: Testing is a first-class citizen in NestJS
- WebSocket support: Real-time communication built-in
- Microservices ready: Build distributed systems with ease
- REST and GraphQL support: Create APIs using your preferred style
Getting Started with NestJS
Installation
To get started with NestJS, you'll need to install the Nest CLI to scaffold your project:
npm i -g @nestjs/cli
nest new my-nest-project
This will create a new project with a standard directory structure. When prompted, you can choose your preferred package manager (npm, yarn, or pnpm).
Project Structure
After installation, you'll have a project structure like this:
my-nest-project/
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
├── package.json
├── tsconfig.json
└── nest-cli.json
Let's understand each key file:
- main.ts: The entry point of the application
- app.module.ts: The root module of the application
- app.controller.ts: A basic controller with a single route
- app.service.ts: A basic service with business logic
Understanding the Main Components
1. Modules
Modules are used to organize the application structure. Each application has at least one module - the root module:
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
2. Controllers
Controllers are responsible for handling incoming requests and returning responses:
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
3. Services
Services contain the business logic and are used by controllers:
// app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
4. Main.ts
The entry file that creates a NestJS application instance:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Building a REST API with NestJS
Let's create a simple REST API for a book collection to demonstrate NestJS in action:
1. Create a Book Module
Generate a new module using the Nest CLI:
nest generate module books
2. Create a Book Model
Create a book.model.ts
file in the books folder:
// books/book.model.ts
export interface Book {
id: number;
title: string;
author: string;
published: number;
}
3. Create a Books Service
Generate a service using the Nest CLI:
nest generate service books
Implement the service:
// books/books.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Book } from './book.model';
@Injectable()
export class BooksService {
private books: Book[] = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', published: 1937 },
{ id: 2, title: '1984', author: 'George Orwell', published: 1949 },
];
findAll(): Book[] {
return this.books;
}
findOne(id: number): Book {
const book = this.books.find(book => book.id === id);
if (!book) {
throw new NotFoundException(`Book with ID ${id} not found`);
}
return book;
}
create(book: Omit<Book, 'id'>): Book {
const newBook = {
id: this.books.length + 1,
...book,
};
this.books.push(newBook);
return newBook;
}
update(id: number, bookData: Partial<Book>): Book {
const book = this.findOne(id);
Object.assign(book, bookData);
return book;
}
delete(id: number): void {
const index = this.books.findIndex(book => book.id === id);
if (index === -1) {
throw new NotFoundException(`Book with ID ${id} not found`);
}
this.books.splice(index, 1);
}
}
4. Create a Books Controller
Generate a controller using the Nest CLI:
nest generate controller books
Implement the controller:
// books/books.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Put,
Delete,
ParseIntPipe
} from '@nestjs/common';
import { BooksService } from './books.service';
import { Book } from './book.model';
@Controller('books')
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Get()
findAll(): Book[] {
return this.booksService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number): Book {
return this.booksService.findOne(id);
}
@Post()
create(@Body() bookData: Omit<Book, 'id'>): Book {
return this.booksService.create(bookData);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() bookData: Partial<Book>,
): Book {
return this.booksService.update(id, bookData);
}
@Delete(':id')
delete(@Param('id', ParseIntPipe) id: number): void {
return this.booksService.delete(id);
}
}
5. Update the Books Module
Update the books module to include the controller and service:
// books/books.module.ts
import { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
@Module({
controllers: [BooksController],
providers: [BooksService],
})
export class BooksModule {}
6. Test the API
With the implementation complete, you can run your NestJS application:
npm run start:dev
Now you can test your API endpoints:
GET /books
- Get all booksGET /books/1
- Get book with ID 1POST /books
- Create a new bookPUT /books/1
- Update book with ID 1DELETE /books/1
- Delete book with ID 1
Advanced NestJS Concepts
1. Dependency Injection
NestJS has a built-in dependency injection system:
@Injectable()
export class BooksService {
// Service implementation
}
@Controller('books')
export class BooksController {
constructor(private readonly booksService: BooksService) {}
// Controller implementation
}
The NestJS DI container automatically resolves the booksService dependency.
2. Pipes
Pipes can be used for data transformation and validation:
// Create a validation pipe for creating books
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class BookValidationPipe implements PipeTransform {
transform(value: any) {
if (!value.title || !value.author || !value.published) {
throw new BadRequestException('Missing required book properties');
}
return value;
}
}
// Use it in the controller
@Post()
create(@Body(new BookValidationPipe()) bookData: Omit<Book, 'id'>): Book {
return this.booksService.create(bookData);
}
3. Guards
Guards determine whether a request will be handled by the route handler:
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
// Check if user is authenticated
return !!request.headers.authorization;
}
}
// Use it in the controller
@UseGuards(AuthGuard)
@Post()
create(@Body() bookData: Omit<Book, 'id'>): Book {
return this.booksService.create(bookData);
}
4. Interceptors
Interceptors add extra logic before or after method execution:
// logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
// Use it in the controller
@UseInterceptors(LoggingInterceptor)
@Get()
findAll(): Book[] {
return this.booksService.findAll();
}
5. Exception Filters
Exception filters handle errors that occur during request processing:
// http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const message = exception.message;
response
.status(status)
.json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
});
}
}
// Use it in the controller
@UseFilters(HttpExceptionFilter)
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number): Book {
return this.booksService.findOne(id);
}
Database Integration with TypeORM
NestJS works seamlessly with TypeORM for database operations. Here's how to integrate it:
1. Install Dependencies
npm install --save @nestjs/typeorm typeorm pg
2. Configure TypeORM in AppModule
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BooksModule } from './books/books.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'bookdb',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // Don't use in production!
}),
BooksModule,
],
})
export class AppModule {}
3. Create a Book Entity
// books/book.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Book {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
author: string;
@Column()
published: number;
}
4. Update the Books Module
// books/books.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Book } from './book.entity';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
@Module({
imports: [TypeOrmModule.forFeature([Book])],
controllers: [BooksController],
providers: [BooksService],
})
export class BooksModule {}
5. Update the Books Service to Use the Repository
// books/books.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Book } from './book.entity';
@Injectable()
export class BooksService {
constructor(
@InjectRepository(Book)
private bookRepository: Repository<Book>,
) {}
async findAll(): Promise<Book[]> {
return this.bookRepository.find();
}
async findOne(id: number): Promise<Book> {
const book = await this.bookRepository.findOneBy({ id });
if (!book) {
throw new NotFoundException(`Book with ID ${id} not found`);
}
return book;
}
async create(bookData: Omit<Book, 'id'>): Promise<Book> {
const book = this.bookRepository.create(bookData);
return this.bookRepository.save(book);
}
async update(id: number, bookData: Partial<Book>): Promise<Book> {
await this.bookRepository.update(id, bookData);
return this.findOne(id);
}
async delete(id: number): Promise<void> {
const result = await this.bookRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(`Book with ID ${id} not found`);
}
}
}
NestJS Architecture Diagram
Here's a visual representation of the NestJS architecture:
Summary
NestJS is a powerful TypeScript framework for building scalable server-side applications. Its architecture is inspired by Angular, making it familiar to frontend developers transitioning to backend development. Key features include:
- A modular architecture allowing for code organization and reuse
- Powerful dependency injection system
- Built-in support for REST APIs, GraphQL, WebSockets, and microservices
- Middleware, pipes, guards, and interceptors for cross-cutting concerns
- Integration with various ORMs like TypeORM and Mongoose
- Built-in testing utilities
NestJS is particularly suitable for enterprise-level applications where code organization, maintainability, and scalability are priorities.
Additional Resources
Here are some resources to continue your NestJS journey:
Exercises
- Create a simple NestJS application that provides CRUD operations for a "Todo" item.
- Add validation to ensure the todo item has a title and is not empty.
- Implement authentication using Passport.js integration with NestJS.
- Create a GraphQL API alongside your REST API for the same data.
- Implement a real-time feature using NestJS WebSockets.
By completing these exercises, you'll gain practical experience with the most important aspects of NestJS development.
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!