Dive into Nest JS: The Ultimate Crash Course for The Impatient

A few years ago, I faced a constant struggle: joining new Node.js teams always meant dealing with a unique setup, often inconsistent and complex. This, as you might guess from reading my previous blog post about Node.js setups, could be incredibly time-consuming and frustrating. Onboarding new team members was just as complicated, with everyone learning unfamiliar configurations.

In search of a better solution, I went on a quest for a "batteries-included" framework akin to Django in Python, Laravel in PHP, or .NET in C#. I wanted something that:

Enforced best practices: Consistent structures and patterns would save time, prevent errors, and improve code maintainability.

Supported TypeScript out of the box: For a more robust and scalable development experience.

Handled authentication out of the box: Eliminate the need to build authentication logic from scratch, saving development time and improving security.

My search led me to NestJS, and it instantly transformed my Node.js development experience. This blog will be your ultimate deep dive into NestJS, exploring its features, components, benefits, and why it's the perfect choice for building robust and scalable Node.js applications. Now that we've established the pain points of inconsistent setups and the desire for a structured framework, let's dive deeper into NestJS itself.

What is NestJS?

NestJS is a JavaScript application framework designed specifically for building efficient and scalable server-side applications using Node.js. It embraces modern development practices, offering several key features:

Progressive JavaScript: the ability to gradually adopt modern JavaScript features while still maintaining compatibility with older.

TypeScript Support: Embraces the benefits of TypeScript for type safety and advanced features, while still allowing pure JavaScript development for flexibility.

Paradigm Blend: NestJS incorporates elements from various programming paradigms like Object-Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP), providing flexibility and diverse tools for different situations.

Opinionated Approach: NestJS guides developers towards best practices by promoting specific patterns and structures, leading to cleaner, more maintainable code. This "opinionated" approach draws inspiration from frameworks in the ASP.NET and Java worlds.

Express Integration: NestJS leverages the popular Express framework under the hood, allowing you to utilize familiar Express concepts like middleware and routing within the NestJS structure.

Batteries Included: NestJS boasts a rich ecosystem with numerous features "out of the box," including support for web sockets, microservices architecture, and extensive testing capabilities. This reduces the need for extensive third-party library integrations.

NestJS provides a robust foundation for building modern and scalable Node.js applications, promoting best practices through its structure and offering a comprehensive feature set without sacrificing developer flexibility. By combining powerful features with familiar paradigms, NestJS empowers developers to focus on business logic and deliver exceptional applications.

Why Embrace NestJS for Your Next Project?

NestJS stands out as a compelling choice for building robust and scalable Node.js applications, offering several key advantages:

1. Enforcing Best Practices:

NestJS promotes well-established design patterns, guiding developers towards a structured and organized codebase. This approach simplifies the process of creating maintainable, scalable, and extensible systems, reducing the risk of technical debt in the long run.

2. Suitable Abstractions:

NestJS provides appropriate abstractions that effectively shield developers from low-level complexities. This simplifies development and allows them to focus on core business logic without getting bogged down in the intricacies of underlying technologies.

3. Opinionated Approach:

NestJS isn't afraid to take a stance on best practices. It encourages consistent patterns and structures across projects, leading to code that's easier to understand, maintain, and collaborate on. This "opinionated" approach, while valuing flexibility, ultimately fosters consistency and coherence within a project and across different teams. This translates to shorter onboarding times and reduced training overhead, especially for those with experience in similar frameworks like Spring Boot, thanks to shared concepts. This is particularly valuable for startups where time is often a critical resource.

4. Enhanced Reusability:

NestJS promotes code reusability at multiple levels:

Code-Level Reusability: The framework offers reusable components and modules, reducing redundancy and streamlining development.

Experience and Knowledge Reusability: By borrowing concepts from established frameworks like ASP.NET, Angular, and Spring, NestJS leverages existing knowledge and experience, shortening the learning curve for developers familiar with these platforms.

In essence, NestJS empowers developers to:

Focus on innovation: Simplified development and enforced best practices free up valuable time and cognitive resources to focus on delivering innovative solutions and features.

Build maintainable systems: The structured approach fosters code that's easier to understand, modify, and scale, ensuring long-term project health.

Leverage existing knowledge: Familiarity with similar frameworks translates into quicker adoption and utilization of NestJS, accelerating development and reducing onboarding times.

By combining these benefits, NestJS presents a compelling choice for developers seeking a structured, efficient, and scalable foundation for their next Node.js application.

Getting Started with NestJS: Setup and Installation

Prerequisites:

Before diving into NestJS, ensure you have the following installed on your system:

  • Node.js: NestJS is built upon Node.js, so you'll need a recent version installed. Download and install it from the official website.

  • ES6 Features: like classes

  • Backend basics

Getting Started:

1. Install Nest CLI:

Once you have Node.js running, you can install the Nest CLI (Command Line Interface) globally using npm:

npm install -g @nestjs/cli

This command installs the Nest CLI tools globally, allowing you to easily create and manage NestJS projects from your terminal.

2. Create a New Nest Project:

With the Nest CLI installed, you can create a new NestJS project using the following command:

nest new <project_name>

Replace <project_name> with your desired project name (e.g., my-awesome-app).

This command will create a new directory named <project_name> with the basic NestJS project structure, including essential files and folders for your application.

Next Steps:

After creating the project, navigate to the project directory using cd <project_name>:

You can then install dependencies and run the application using the following commands:


npm install
npm run start

These commands will install the required dependencies and start your NestJS application in development mode. You can then access your application in your browser, typically at http://localhost:3000.

Mastering the NestJS CLI: Essential Commands

The NestJS CLI is a powerful tool that streamlines project creation, development, and management. Here's a breakdown of some key commands you'll use frequently:

1.nest new <project_name>:

Purpose: Scaffolds a new NestJS project with the specified name, providing the fundamental directory structure and essential files to kick-start your application.

2.nest start:

Purpose: Starts the development server, transpiling your TypeScript code into JavaScript for execution.

--watchflag: Enables automatic transpilation whenever you make changes to your code, ensuring your application reflects the latest updates without manual intervention.

3.nest generate <element>:

Purpose: Generates various building blocks of your NestJS application, including :

Modules: Organize your application logic and functionality.

Controllers: Handle incoming API requests and responses.

Pipes: Transform and validate data before it reaches your controllers.

Guards: Implement authorization logic to control access to specific application parts.

Providers: Create services and utility functions used throughout your application.

Services: Encapsulate reusable business logic and interact with external resources like databases.

Test file inclusion: Importantly, this command also generates a corresponding test file for the specified element, promoting good testing practices from the outset.

With these fundamental CLI commands, you'll be well-equipped to navigate the development process and build robust NestJS applications efficiently. If you want to see more of these check out the official docs.

NestJS Project Structure: Files and Scripts Explained

NestJS offers a well-organized project structure to streamline development and maintainability. This section delves into essential files and scripts within our newly created NestJS project:

1.package.jsonScripts:

NestJS package.json scripts

npm run build: Transpiles your TypeScript code into JavaScript for production use. The generated code resides in the dist folder, ready for deployment to your server.

npm run format: Leverages the prettier library to format your code consistently, enforcing a uniform style guide across your project. This promotes code readability and improves team collaboration. Ideally, you'd run this before committing changes or pushing code.

npm run start: Starts the NestJS web server using the transpiled code in the dist folder. This command is typically used on the server where your application runs.

npm run start:dev: Similar to npm run start, but includes the --watch flag. This enables a file watcher that automatically rebuilds your code and restarts the server whenever you make changes, streamlining the development workflow.

2. Application Files:

srcFolder: Holds all of your application's source code, categorized into different files.

3. Core Application Files:

nestJS App src folder

nestJS App src folder

I'll make an effort to provide a brief explanation of the files in your src folder. When we get to the nest's construction blocks, we'll dig deeper;

main.ts: The entry point of your application. When you run nest start, it executes this file, finding and instantiating the root module (AppModule) to create the application instance. It also sets up global middleware and starts the Express server on the specified port (3000 by default). This file contains the bootstrapping logic, keeping it separate from application-specific concerns, making it easier to modify or test in isolation following the Encapsulation & Single Responsibility Principle (SRP).

app.controller.ts: A basic controller providing an endpoint at http://localhost:3000. The controller focuses on handling requests and responses, delegating business logic to the injected AppService (see below). Inversion of Control(IoC) helps manage dependencies, improving testability and flexibility.

app.controller.spec.ts: The corresponding test suite for the AppController. These test suites are automatically generated when you create elements using nest generate, providing a starting point for writing unit tests.

app.service.ts: A simple injectable provider that returns a "hello world" message. In real-world scenarios, service classes handle the bulk of your application's business logic.

app.module.ts: The root module, acts as the dependency injection container explicitly managing dependencies and their lifecycles. It registers all your controllers and providers for the application to use. This AppModule is typically maintained and other modules are imported into it. This improves code testability and makes the system more flexible to change.

4. NestJS-Specific Files:

nest-cli.json: Configures the Nest CLI, allowing customization of project generation settings or default CLI behaviors (e.g., default file naming conventions).

tsconfig.json: The base TypeScript configuration file, controlling the compilation behavior of your TypeScript code.

**tsconfig.build.json: ** Occasionally, this file may exist to provide specific configuration for the TypeScript compiler used in the build process.

public: May contain static assets to be served directly by the web server.

3. Testing-Related Files:

test/*: You might have a dedicated test folder containing different types of tests:

.e2e-spec.ts: End-to-end tests that simulate real user interactions with your application through the browser.

Other integration test files: These might focus on testing how different components of your application interact with each other.

4. Other Files:

.eslint.js*: allows you to define configuration rules for your project using various formats. This format offers a concise and readable way to specify your desired coding style and enforce best practices. You can go ahead and add linters to the project to enforce your team code styles

Note: This is not exhaustive. As your project grows, you might introduce new file types or folders to organize things like:

NestJS Module Structure: Feature-Based Organization

NestJS adopts a per-feature module approach, similar to Angular and Django. This means that all the elements needed to implement a single feature (like CRUD operations for a database model) are grouped within a dedicated folder and bundled into a corresponding module.

Comparison with Traditional Layered Structure:

Traditional approaches, often seen in ASP.NET, Java, and elsewhere, organize classes by their type (e.g., controllers, services, models) in separate folders. Here's an example:

Controllers/
|--- TodoController.cs
Services/
|--- TodoService.cs
Models/
|--- TodoEntity.cs

NestJS Approach:

In NestJS, the structure looks different, grouping elements by feature:

todo/
|--- todo.controller.ts
|--- todo.entity.ts
|--- todo.module.ts
|--- todo.service.ts

Benefits of Per-Feature Modules:

Improved Discoverability: Grouping related code by feature makes it easier to find and understand specific functionalities within your application.

Enhanced Reusability: Entire modules, representing complete features, can be easily moved around, integrated into other projects, or packaged as libraries.

Clear Delegation: Feature modules facilitate assigning ownership and development responsibilities to individual teams or members.

This approach makes it rather painless to pick up and move large portions of functionality to new codebases or into packageable libraries, and also makes it easy to split up code responsibilities among teams or team members.

Core Building Blocks of NestJS

As highlighted by NestJS's founder, the framework offers several key building blocks that empower developers to efficiently construct robust and scalable applications. Here's a breakdown of these essential elements:

Modules

Modules are where all of the controllers and providers are registered, along with all of the imported sub-modules which contain their own controllers and providers. Modules hook up the dependency injection containers and enable the resolving of required dependencies for controllers and providers. In NestJS, modules are created by decorating a class with the @Module decorator. A simple example of a Module class would look like this:

//sample.module.ts
@Module({
    imports: [DatabaseModule],
    controllers: [TodoController],
    providers: [TodoService],
    exports: [TodoService],
})
export class TodoModule  {}

The @Module decorator takes in a metadata object with four properties: • imports: Importing other @Module classes allows for using other sub-modules and creating the hierarchy of functionality. For instance, the AppModule will be importing the TodoModule, enabling the TodoModule functionality to be enabled at the root of the application. This is also how third-party modules such as @nestjs/typeorm, @nestjs/config, and others are wired into your application. • controllers: The controller's array contains the @Controller classes that are registered to this module. By defining them in this array, they are made accessible via HTTP requests. • providers: The provider's array contains all @Injectable classes such as data services, instance factories, pipes, guards, middleware, and interceptors. • exports: This array allows for making providers and imports registered to this module available for use outside of the current module. For example, a TodoService registered to a TodoModule is only available for classes inside the TodoModule. Once TodoService is added to the exports array, it is now available to outside modules. (This is the main difference between nest and angular)

Controllers

Controllers are responsible for handling incoming requests and returning responses to the client. Controllers can group related request-handling logic into a single class. For example, a TodoController class might handle all incoming requests related to users, including showing, creating, updating, and deleting to-dos. They are responsible for interpreting input, passing the request details to services, and then formatting the service results in a way that can be exposed as responses. In the case of NestJS, “Controller” specifically refers to a class that handles HTTP requests. In NestJS, controllers are decorated with the @Controller decorator. By passing a string into the @Controller argument, we define a base route path for all of the routes inside the controller. The routes inside this class are notated with HTTP method decorators and path designations. It is best practice to keep controllers relatively “thin” and avoid putting significant business logic, data logic, or otherwise processing logic in your controller route methods: that work belongs in a provider service. By keeping the controller’s responsibility clearly defined as input and output transformation, it becomes easier to write, read, design, and test your code. A simple example of a Controller class would look like:

//sample.controller.ts
@Controller('api/app')
export  class  AppController  {
    constructor(private  readonly  appService:  AppService)  {}

    @Get('health')
    getHealth():  HealthCheck  {
        return  this.appService.getHealth();
    }
}

Providers

Providers is a blanket term in NestJS that encompasses service classes, repositories, factories, utility classes, and anything of that nature. This also includes important NestJS elements like middleware and guards. These are marked with the @Injectable() decorator. A provider is an instruction to the dependency injection system on how to obtain a value for dependency it can be a simple injectable class or a factory, third-party library.

Services

Services contain the core business logic of your application, handling complex operations, interacting with databases or other external resources, and generally orchestrating application logic.

Others

Interceptors

These inspect, enhance, or transform HTTP requests from the client to the applications or transform responses on their way back to the client.

Guards

These control the request behavior. So when a request is sent before it can proceed each guard checks if the request should be allowed to proceed for instance extract the json-web-token.

Pipes

Pipes in NestJS transform request parameters at a method level. While you can create your own, the @nestjs/common package comes with very helpful pipes that cover the most common use cases. • ValidationPipe: provides request validation using the class-validator library. • ParseIntPipe: parses the route parameter to a number type. • ParseBoolPipe: parses the route parameter to a boolean type. • ParseArrayPipe: parses the route parameter to an array of a given type. • ParseUUIDPipe: parses the route parameter to a UUID of a provided version. • DefaultValuePipe: allows for a default value if no value is supplied in the route.

Building a NestJS Todo List API

Let's create a simple Todo List API in NestJS to solidify our understanding of its core concepts. Here's a step-by-step approach:

1. Project Setup:

Prerequisites: Ensure you have Node.js and npm (or yarn) installed on your system.

Create Project: Open your terminal and run:

nest new todo-api

This creates a new NestJS project named todo-api

Creating Todo directory and files

To start building our custom Todo module, let’s first create the empty files we will need. While the NestJS CLI has some excellent generation tools that we can use (which we will review later!), for this exercise we will create them by hand to demonstrate how these files all work together. Inside of your application directory, make the following directory and files:

mkdir src/todo/
touch src/todo/todo.controller.ts
touch src/todo/todo.interface.ts
touch src/todo/todo.module.ts
touch src/todo/todo.service.ts

Optionally you can use the Nest CLI. The Nest CLI simplifies this process quite a bit and we can accomplish the same module setup with the following commands:

nest generate module todo
nest generate controller todo
nest generate service todo
cd todo && nest generate interface todo

Create the Todo interface

Inside of the todo/todo.interface.ts file, we will define the shape of the To-do JSON object. Note that interfaces are different from classes in that interfaces are “abstract” - there is no JavaScript code output and they are purely used for the TypeScript compiler and IntelliSense tooling - while classes are actual instantiations in memory. To create an interface, use the TypeScript keyword interface. Our To-do class will have three properties:
• an optional id (optional new To-do requests will auto-assign the id)
• a label string
• a complete Boolean flag We will add these to the interface as such:

//todo.interface.ts
export interface  Todo  {
    id?:  number;
    label:  string;
    complete:  boolean;
}

Create the TodoService data service

Following the prescribed NestJS architecture pattern, we will create a TodoService class that handles the data logic for maintaining our To-dos. Inside of the todo.service.ts file, we will define an exported TypeScript class, TodoService, and decorate it with the @Injectable decorator.

//todo.service.ts
import  {  Injectable  }  from  '@nestjs/common';

@Injectable()
export  class  TodoService  {
}

We will be using an in-memory array as our storage mechanism. This will not persist and will be cleared out every time the development server is restarted, but it will serve as a simple mechanism to start your NestJS journey. To create this array, we will define and instantiate a storage class-level variable. Once added, your todo.service.ts class should look as such:

// todo.service.ts
import  { Injectable }  from  '@nestjs/common';
import  { Todo }  from  './todo.interface';

@Injectable()
export class TodoService  {
    private storage: Todo[] = [];
}

Note: that NestJS providers are singletons by default and any injection of the TodoService will share the same in-memory array

Create theTodoControllerREST API Controller

We now need a Controller to handle our HTTP requests. To do so, we create and export a TodoController class that is decorated with the NestJS @Controller decorator with the base route path of “todo”.

// todo.controller.ts
import  {  Controller  }  from  '@nestjs/common';

@Controller('todo')
export  class  TodoController  {
}

To leverage the TodoService we created, we will use NestJS’ dependency injection through constructor injection. Using TypeScript’s constructor assignment feature, we can easily set the provider as a private, readonly class-level instance property.

//todo.controller.ts
import  {  Controller  }  from  '@nestjs/common';
import  {  TodoService  }  from  './todo.service';

@Controller('todo')
export  class  TodoController  {
    constructor(private  readonly  todoService:  TodoService)  {}
}

NestJS will analyze the parameters in the constructor, find the types in the dependency injection container, and resolve them. We will now be able to use the TodoService in-controller methods via this.todoService. To validate our module, we will set up a single GET route that returns an empty array (note the added import). Routes are enabled using HTTP method decorators: @Get, @Post, @Put, @Patch, and @Delete. Just like the parameter passed to @Controller, the value passed to the method decorator defines the path of the route. In the case of this GET request, we will leave it as undefined, ultimately resolving to the base route path of /todo.

// todo.controller.ts
import  {  Controller,  Get  }  from  '@nestjs/common';
import  {  TodoService  }  from  './todo.service';
import  {  Todo  }  from  './todo.interface';

@Controller('todo')
export  class  TodoController  {
    constructor(private  readonly  todoService:  TodoService)  {}

    @Get()
    findAll():  Todo[]  {
        return  [];
    }
}

Create theTodoModulemodule

With our Controller and Provider classes created, we now need to create a class and decorate it with the @Module decorator to register them to the NestJS application. To do so, we’ll create a TodoModule class and decorate it with the @Module decorator.

// todo.module.ts
import  {  Module  }  from  '@nestjs/common';

@Module({})
export  class  TodoModule  {}

Now we can add the TodoService as a Provider to this module to make it available for injection.

// todo.module.ts
import  {  Module  }  from  '@nestjs/common';
import  {  TodoService  }  from  './todo.service';

@Module({
    providers:  [TodoService],
})
export  class  TodoModule  {}

With the TodoService now registered, we can register the TodoController which will resolve the service as a dependency.

//todo.module.ts
import  {  Module  }  from  '@nestjs/common';
import  {  TodoController  }  from  './todo.controller';
import  {  TodoService  }  from  './todo.service';

@Module({
    controllers:  [TodoController],
    providers:  [TodoService],
})
export  class  TodoModule  {}

Import TodoModule into AppModule

We now have a contained module of functionality, from the HTTP request to the data storage. However, the root module AppModule we saw in the previous chapters has no reference to TodoModule. As we know, our entry point main.ts creates the INestApplication from the AppModule, so without this reference, our TodoModule logic is unavailable. To create this link between AppModule and TodoModule, open the app.module.ts file. In this @Module decorator, add TodoModule to the imports array.

// app.module.ts
import  {  Module  }  from  '@nestjs/common';
import  {  AppController  }  from  './app.controller';
import  {  AppService  }  from  './app.service';
import  {  TodoModule  }  from  './todo/todo.module';

@Module({
    imports:  [TodoModule],
    controllers:  [AppController],
    providers:  [AppService],
})
export  class  AppModule  {}

If the development server is not still running, start it by running npm run start:dev. In the console output, you should see Nest wiring up the TodoModule and creating the TodoController route:

With your server still running, open the Postman application. Make a new request to the TodoController route at http://localhost:3000/todo. The controller will return a JSON response of an empty array, which we return in the findAll() method. We are now resolving HTTP requests to our TodoController and are ready to start adding the CRUD actions to the controller and service.

//todo.controller.ts
import  {
Controller,
Get,
} from  '@nestjs/common';
import  {  TodoService  }  from  './todo.service';
import  {  Todo  }  from  './todo.interface';

@Controller('todo')
export  class  TodoController  {
    constructor(private  readonly  todoService:  TodoService)  {}

    @Get()
    findAll():  Todo[]  {
        return  this.todoService.findAll();
    }
}

Ensure that the development server is still running in your command-line tool. Open Postman and make a GET request to http://localhost:3000/todo. In response, we’ll get an empty array (again).

create()

Add the POST route

With the data service method in place, we now need to enable an HTTP endpoint in our controller to call it. To do this, we will create a @Post route on the TodoController. Following REST standards, we’ll configure this route to be POST/todo. With @Post, @Put, and @Patch routes, the HTTP content body is used to transfer data objects, such as JSON, XML, or text. In this example, we will be sending a JSON object of a Todo item. For NestJS to parse and interpret the content body, the decorator @Body should be applied to the route method parameter. NestJS will apply JSON.parse() to the content and provide you a JSON object to work with. Since we expect this payload to match our Todo interface, we will type it as such.

//todo.controller.ts
import  {
Body,
Controller,
Get,
Post,
} from  '@nestjs/common';
import  {  Todo  }  from  './todo.interface';
import  {  TodoService  }  from  './todo.service';

@Controller('todo')
export  class  TodoController  {
    constructor(private  readonly  todoService:  TodoService)  {}

    @Post()
    create(@Body()  todo:  Todo):  void  {
        return  this.todoService.create(todo);
    }

    @Get()
    findAll():  Todo[]  {
        return  this.todoService.findAll();
    }
}

We can now send a POST request to http://localhost:3000/todo with a JSON payload to add it to the storage array. In Postman, do the following to set up your POST request:

1. Create a new request tab.

2. Change the GET value in the HTTP method dropdown to POST.

3. Enter the http://localhost:3000/todo value in the URL.

4. In the Body tab, select raw. In the dropdown to the right of binary, select “JSON(application/json)”.

In the text-area, supply the following body:

{
    "label":  "Create  an  awesome  API",
    "complete":  false
}

Send the request. Our response will be empty but with a status of 201 Created. We can now make a GET request to http://localhost:3000/todo again to see our new To-do item in our array with a set id of 1.

Quick aside: Adding the NestJS Logger

You may have noticed that there was no indication in our command-line console that the request was processed. While this is likely ideal in most scenarios, it would be very helpful for us to see that we are hitting the routes that we expect. NestJS provides a Logger implementation that makes it easy to add logging to your application. It is best practice to use this Logger class over the console log methods because the log messages are formatted cohesively, and the Logger allows you to swap out functionality or disable logging completely at a global level with a simple configuration in your main.ts file. To add logging to our controller, create a new instance of a Logger as a class-level variable on the TodoController. We will add a log statement to each of our methods as well.

// todo.controller.ts
import  {
Body,
Controller,
Get,
Logger,
Post,
} from  '@nestjs/common';
import  {  Todo  }  from  './todo.interface';
import  {  TodoService  }  from  './todo.service';

@Controller('todo')
export  class  TodoController  {
    private  readonly  logger  =  new  Logger(TodoController.name);

    constructor(private  readonly  todoService:  TodoService)  {}

    @Post()
    create(@Body()  todo:  Todo):  void  {
        this.logger.log('Handling create() request...');
        return  this.todoService.create(todo);
    }

    @Get()
    findAll():  Todo[]  {
        this.logger.log('Handling findAll() request...');
        return  this.todoService.findAll();
    }
}

Now when we make a request to either method, we will see items in our console such as:

[TodoController]  Handling  create()  request...
[TodoController]  Handling  findAll()  request...

findOne()

Add the GET route

We will now need to update the TodoController accordingly to enable this service method through an HTTP request. In todo.controller.ts, we will add a new @Get method, but this time we will take in a route parameter in the @Get decorator. Route parameters are variables inside of a given path. Routers use pattern matching to interpret and parse these paths and pluck out the variables in the provided route pattern. To access this variable in your NestJS route method, the @Param decorator is applied to the method argument, supplying the name of the route parameter. In our API, we want to supply the To-do object’s id as a route parameter, so we use the route pattern of :id to indicate a parameter variable with an id, and then use the @Param('id') decorator to hook the value into the method argument.

//todo.controller.ts
import  {
Body,
Controller,
Get,
Logger,
Param,
Post,
} from  '@nestjs/common';
import  {  Todo  }  from  './todo.interface';
import  {  TodoService  }  from  './todo.service';

@Controller('todo')
export  class  TodoController  {
    // ...

    @Get(':id')
    findOne(@Param('id')  id:  number):  Todo  {
        this.logger.log('Handling findOne() request with id='  +  id  +  '...');
        return  this.todoService.findOne(id);
    }
}

Since the storage array was cleared when we restarted our development server, we will do the following to test:

Send the POST request outlined in the create() section to create a new To-do item.

Send a GET request to http://localhost:3000/todo to see all the items in storage, including your new To-do.

Send a GET request to http://localhost:3000/todo/1, our new route, to retrieve the To-do item directly. Uh oh, the response is empty! Our command-line console says we hit the route with expected ID ([TodoController] Handling findOne() request with id=1...) . So, what happened? It is important to understand that by default, @Param values are strings. While we gave it the type of number, recall that TypeScript types are abstract and have no impact on the actual executing code; this is to say that the executing code had no way of knowing it should have parsed the route parameter id to a number. This is where the @Pipe decorator comes in.

To fix our findOne() endpoint, we need to add the ParseIntPipe to our @Param pipe.

//todo.controller.ts
import  {
Body,
Controller,
Get,
Logger,
Param,
ParseIntPipe,
Post,
} from  '@nestjs/common';
import  {  Todo  }  from  './todo.interface';
import  {  TodoService  }  from  './todo.service';

@Controller('todo')
export  class  TodoController  {
// ...
    @Get(':id')
    findOne(@Param('id',  ParseIntPipe)  id:  number):  Todo  {
        this.logger.log('Handling findOne() request with id='  +  id  +  '...');
        return  this.todoService.findOne(id);
    }
}

This will parse the value to a number and now our .find() method in our data service will find an exact equality against the Todo item we POST. Since the storage array was cleared when we restarted our development server, we will do the following to test:

Send the POST request outlined in the create() section to create a new Todo item.

Send a GET request to http://localhost:3000/todo to see all the items in storage, including your new Todo.

Send a GET request to http://localhost:3000/todo/1, our new route, to retrieve the To-do item directly. With this pipe in place, we’re now able to retrieve the To-do item.

Integrating User Interface

To test with this user interface, start up your development web server with npm run start:dev. In the user interface, try to create a new To-do item by typing in the “What’s next?” input and hitting enter. Uh oh, nothing happened!

Enable CORS

If you check out your browser console, you’ll see some errors about a missing Access-Control-Allow-Origin header. These errors are due to cross-origin reference sharing (CORS) restrictions that prevent web applications from making XHR requests to other domains. These restrictions are a security feature browsers have put in place using what are known as pre-flight requests and API response headers. Pre-flight requests are OPTION HTTP requests that are executed before the actual XHR request. Your browser automatically injects this request and if the restrictions specified in the response headers are met by the request parameters, then the actual request will continue as expected. By default, these restrictions prevent requests from any other origin; in our case, our server at localhost is preventing XHR requests from stackblitz.com. NestJS makes opening these CORS restrictions simple. In your main.ts file, call app.enableCors(); in the bootstrap() method. This will add headers to your controller route responses that enable cross-domain requests.

// main.ts

import  {  NestFactory  }  from  '@nestjs/core';
import  {  AppModule  }  from  './app.module';

async function bootstrap()  {
    const app = await NestFactory.create(AppModule);
    app.enableCors();
    await  app.listen(3000);
}
bootstrap();

Resources

Building a platform: NestJS from the ground up | Kamil Mysliwiec | JS Poland 2018
Wanago Blog
Code with Vlad's book
Radiansys blog

0
Subscribe to my newsletter

Read articles from Atuhaire Collins Benda directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Atuhaire Collins Benda
Atuhaire Collins Benda