How to Structure Your First NestJS Application

Updated: December 31, 2023 By: Guest Contributor Post a comment

Introduction

Designing a well-structured application is crucial for ensuring maintainability and scalability. This tutorial will guide you through the process of structuring your first NestJS application using the latest TypeScript syntax, providing a solid foundation to build upon.

Setting Up Your Project

First, you’ll need to install the Nest CLI which is a powerful command-line interface that helps you to initialize and develop your application with ease. Run the following command:

npm i -g @nestjs/cli

Now, create a new project using the CLI:

nest new my-first-nestjs-app

This command scaffolds a new NestJS project with a default structure that we’ll explore and expand upon.

Exploring Project Structure

Once the project is created, navigate to the root directory and you’ll find several directories and files:

  • src: Contains your application’s source code.
  • main.ts: The entry file of your application which uses the core function NestFactory to create a NestJS application instance.
  • app.module.ts: Known as the root module, it organizes various components of your NestJS app.

Other files include configuration for TypeScript, testing, and dependencies.

Modules: Building Blocks of NestJS

Modules are fundamental in NestJS as they encapsulate providers, controllers, and other modules. They organize your code into distinct features or sets of capabilities. Here’s how to define a simple module:

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

@Module({})
export class AppModule {}

Now let’s add a controller to the module.

Controllers: Handling Requests

Controllers are responsible for handling incoming requests and returning responses to the client. Below is an example of a basic controller:

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

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

To include this controller in a module, you’d update app.module.ts:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';

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

Controllers can also receive path parameters, query parameters, and body data. Here’s an example with path parameters and service dependency injection:

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

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

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

Notice how we injected CatsService. Services contain business logic and are part of NestJS’s providers.

Providers: Services and Dependency Injection

Services in NestJS are typically providers that can be injected into controllers using dependency injection. Create a simple service for our Cat entity as follows:

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

@Injectable()
export class CatsService {
  private readonly cats = [];

  findCat(id: string): string {
    return this.cats.find(cat => cat.id === id);
  }

  findAllCats(): string[] {
    return this.cats;
  }
}

Add this service to your module to make it available:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

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

Now, your service will be injectable in any controller or other services within the module.

Middleware, Filters, and Pipes

NestJS also offers middleware, exceptions filters, and pipes to handle logging, error handling, and validation respectively.

Middleware

Middleware functions can run before the route handlers and have access to the request and response objects. Here’s a simple logging middleware:

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();
  }
}

Apply middleware within a module using the configure method:

import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

Error handling, filters, and validation are equally simple to configure within the NestJS framework using the same declarative style.

Conclusion

To wrap up, structuring your first NestJS application involves understanding and implementing modules, controllers, and providers effectively. With this structured foundation, you have the power to build scalable and maintainable server-side applications. Continue exploring NestJS to harness the full potential of this robust framework.