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.
--watch
flag: 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.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:
src
Folder: Holds all of your application's source code, categorized into different files.
3. Core Application Files:
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 theTodoController
REST 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 theTodoModule
module
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
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