Welcome to this NestJS tutorial for absolute beginners. In the following you’ll learn NestJS from the ground up which means that we’ll go through all steps which are necessary to get NestJS installed, create a new NestJS project from scratch and implement a first example from start to finish.
NestJS is a progressive Node.js framework for building efficient, reliable and scalable server-side applications. The framework fully supports TypeScript and under the hood it makes use of the Node.js framework Express.
NestJS introduces another level of abstraction on top of Node.js and Express and further helps you to structure your back-end codebase. The project’s website can be found at https://nestjs.com/ :
Installing NestJS
Before you can use NestJS to create your back-end application we need to make sure that the Nest CLI (Command Line Interface) is installed on your system. This can be done by using the Node.js Package Manager NPM in the following way:
$ npm i -g @nestjs/cli
Of course, you need to make sure that Node.js and NPM is already installed on your system. If this is not the case yet, just follow the instructions on https://nodejs.org/ to install Node.js and NPM on your computer.
Once the installation of the Nest CLI is completed the nest
command will be available. You can use this command in the following way to initiate a new NestJS project:
$ nest new my-nestjs-01
Executing this command creates a new folder my-nestjs-01 in the current location and downloads the default project template into this folder.
Let’s take a closer look at the initial project structure.
Project Structure
The initial structure of the project consists of the following folders and files:
The most important folder of the project is the src folder. This is the place where you’ll find the TypeScript files which is application code inside. Later, when implementing our first example we’ll spend most of the time working in this directory.
Inside the src folder you can find five files in the initial project setup:
- main.ts: Entry point of application. By using the NestFactory.create() method a new Nest application instance is created.
- app.module.ts: Contains the implementation of the application’s root module.
- app.controller.ts: Contains the implementation of a basic NestJS controller with just one route.
- app.service.ts: Contains the a basic service implementation.
- app.controller.spec.ts: Testing file for controller.
Let’s take a closer look at the code:
In file main.ts you’ll find the following default implementation:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
This is the entry point of the application. First of all NestFactory is imported from the @nestjs/core library. Furthermore AppModule is imported from the app.module.ts file of our project.
Second, the bootstrap function is implemented and marked as async. Inside this function the NestFactory.create() method is called and the root application module AppModule is passed into that function call as an argument. This is creating a new NestJS application instance with the module attached. To start up the server the next step is to call the listen method and pass in the port on which the web server should be running, e.g. port 3000. Because the listen method is returning a promise when the server has been started successfully we’re using the await keyword here.
Finally the file contains the call of the bootstrap function, so that the code is executed.
Next let’s take a look at the implementation of the root application module which you can find inside the file app.module.ts:
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
To declare class AppModule as a module the @Module decorator is used which is imported from the @nestjs/common library. An object with three properties is passed into the @Module decorator. The three properties are:
- imports
- controllers
- providers
All controllers which should be part of AppModule needs to be part of the array which is assigned to the controllers property (in the initial state of the application there is only one controller which is assigned to the root module: AppController).
Services which should be available in AppModule must be listed in the array which is assigned to the providers property.
Next we’re taking a closer look at the AppController implementation in app.controller.ts:
@Controller()
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
This is a very simple implementation of a NestJS controller which consists of just one GET route. In order to make a class a controller you need to add the @Controller decorator. This decorator is imported from the @nestjs/common library.
A controller of relies on a service class. In this default example AppController makes use of a service named AppService. AppService is being implemented in file app.service.ts and therefore a corresponding import statement needs to be added on top.
By using the concept of Dependency Injection AppService is inserted into AppController (by adding a constructor parameter of that type).
A default route is implemented by implementing the getHello method. In order to declare that this method handles a HTTP GET request the @Get docorator is added to this method. The method is using the getHello method from AppService to retrieve data and at the same time return that data as a response.
Let’s move on and see what’s inside app.service.ts:
@Injectable()
getHello(): string {
return 'Hello World!';
}
}
This file is containing the implementation of the AppService class. In order to make AppService a service class (which can be injected in a controller like seen before) the @Injectable decorator needs to added before that class. Again this decorator type is imported from the @nestjs/common package.
The service class implementation is very simple and only consists of the implementation of the getHello method. This method is just returning the static string “Hello World!”. In a real world scenario a service method would of course be used to retrieve data e.g. from database, a web service or some other data source.
Now, that’ve a first impression of the most important building blocks of the default NestJS application we’re ready to start up the server and see what we’re getting as a result.
Running The Application
To start the the server you simply need to execute the following command within the project folder:
$ npm run start
You should then see the following output on the command line:
Finally you should see the message “Nest application successfully started”. This message informs you that the server is ready and you can try sending the first GET request to the default endpoint by simply using the browser and opening URL http://localhost:3000 The result can be seen in the following screenshot:
No surprises here: The text which is being returned is “Hello World!”. This string is retrieved from the getHello service method and then return as a response of the HTTP GET request by the corresponding controller method.
There is another option to start up the server:
$ npm run start:dev
If you’re using the start:dev script nodemon is used to start up the server which means that all of your code files are being monitored for changed. The server is automatically restarted when changes in your source code are detected.
Adding A New Endpoint
In the next step we’re going to use the new NestJS project to add a new endpoint. The endpoint which we’re going to add should accept the HTTP GET, POST, and DELETE requests and will be used to manage data of online courses. The following endpoints will be created to cover those requirements:
- /courses - Endpoint accepting HTTP GET requests to retrieve a list of all available online courses
- /courses/[courseId] - Endpoint accepting HTTP GET request to retrieve the online course with a specific course ID
- /courses - Endpoint accepting HTTP POST requests to add new courses
- /courses - Endpoint accepting HTTP DELETE request to remove courses. The course which should be removed needs to be specified by it’s ID which is added to the request by using a query parameter
Creating A New Module
As NestJS allows us to organize our code in modules it’s a good idea to start the implementation with the creation of a new module:
$ nest generate module courses
Executing this command is adding a new file to the project: /src/courses/courses.module.ts:
Inside this file you can find the following default implementation of an empty module named CoursesModule:
@Module({})
The following import statement is added into app.module.ts automatically, so that CoursesModule is added to the NestJS application:
Furthermore you’ll notice that CoursesModule is added to the array which is assigned to the imports property of the @Module decorator:
@Module({
imports: [CoursesModule],
controllers: [AppController],
providers: [AppService],
})
Creating A New Controller
Let’s add a new controller to CoursesModule by using the following command:
$ nest g controller courses
Executing this command will show you the following output:
Here you can see which files have been added to the project. The main controller implementation is available in file courses.controller.ts:
@Controller('courses')
This is just an empty controller implementation. We can now use this file to add code which is needed to cover our requirements.
Again, CoursesController is added automatically to CoursesModule like you can see in the following:
@Module({
controllers: [CoursesController]
})
Mock Data
Of course we need to prepare some sample courses data which can be returned when the user is accessing the corresponding endpoints:
Create a new file inside of the src/courses folder within your project structure: courses.mock.ts and insert the following content:
{ id: 1, title: 'NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)', description: "Master Node JS, build REST APIs with Node.js, GraphQL APIs, add Authentication, use MongoDB, SQL & much more!", author: 'Maximilian Schwarzmüller', url: 'https://codingthesmartway.com/courses/nodejs-complete-guide/' },
{ id: 2, title: 'The Complete Web Developer in 2020: Zero to Mastery', description: "Learn to code and become a Web Developer in 2020 with HTML, CSS, Javascript, React, Node.js, Machine Learning & more!", author: 'Andrei Neagoie', url: 'https://codingthesmartway.com/courses/web-developer-2018/' },
{ id: 3, title: 'Learn and Understand NodeJS', description: "Dive deep under the hood of NodeJS. Learn V8, Express, the MEAN stack, core Javascript concepts, and more.", author: 'Anthony Alicea', url: 'https://codingthesmartway.com/courses/learn-understand-nodejs/' },
];
Setting Up A New Service
Data access will be management by a service, so the next step is to generate a service class by using the nest command again:
$ nest generate service courses
This command is adding a new file courses.service.ts to the project and inserting the following code into this file:
@Injectable()
In order to make CoursesService part of CoursesModule the service is automatically added in file courses.module.ts:
@Module({
controllers: [CoursesController],
providers: [CoursesService]
})
Let’s start to implement CoursesService step by step. First we need to get access to the courses sample data array available in file courses.mock.ts:
@Injectable()
courses = COURSES;
}
Get Courses
The two service methods getCourses() and getCourse(courseId) are being implement to retrieve data:
getCourses(): Promise<any> {
return new Promise(resolve => {
resolve(this.courses);
});
}
getCourse(courseId): Promise<any> {
let id = Number(courseId);
return new Promise(resolve => {
const course = this.courses.find(course => course.id === id);
if (!course) {
throw new HttpException('Course does not exist', 404)
}
resolve(course);
});
}
The getCourses() method is used to return the complete list of courses via a Promise. The getCourse(courseId) method is retrieving just one single course by its id. Again the result is returned via Promise. In case the id which is passed into that method via the courseId parameter is not existing a HTTP Status Code 404 response is returned.
Add Course
The next service method which needs to be implemented is addCourse(course). This method is used to add new courses to the courses array. The return type of this method is again a Promise which is resolved once the data has been added successfully:
addCourse(course): Promise<any> {
return new Promise(resolve => {
this.courses.push(course);
resolve(this.courses);
});
}
Delete Course
Last but not least a service method deleteCourse(courseId) is implemented to remove a specific course item from the list of courses. The corresponding implementation can be seen in the following:
deleteCourse(courseId): Promise<any> {
let id = Number(courseId);
return new Promise(resolve => {
let index = this.courses.findIndex(course => course.id === id);
if (index === -1) {
throw new HttpException('Course does not exist', 404);
}
this.courses.splice(index, 1);
resolve(this.courses);
});
}
Updating CoursesController:
CoursesService can now be used to retrieve, add, or remove courses data. This is exactly the functionality which is needed for the endpoints which should be implemented. Let’s make use of CoursesService and use the CoursesController class to add the needed endpoints:
@Controller('courses')
constructor(private coursesService: CoursesService) {}
@Get()
async getCourses() {
const courses = await this.coursesService.getCourses();
}
@Get(':courseId')
async getCourse(@Param('courseId') courseId) {
const course = await this.coursesService.getCourse(courseId);
return course;
}
@Post()
async addCourse(@Body() createCourseDto: CreateCourseDto) {
const course = await this.coursesService.addCourse(createCourseDto);
return course;
}
@Delete()
async deleteCourse(@Query() query) {
const courses = await this.coursesService.deleteCourse(query.courseId);
return courses;
}
}
This is the complete source code which is needed in courses.controller.ts to cover our requirements. Let’s go through it step by step:
The first thing you should notice is that CoursesService is injected into CoursesController, so that we’re able to make use of the service methods.
The first endpoint /courses is implemented by adding the getCourses() Method. To specify that this method should handle an incoming HTTP GET request the @Get() decorator is added. Inside this method we’re using the getCourses service method from CoursesService to retrieve the list of courses. As the service method is returning a Promise, we need to use the await keyword here and also at the same time declare the method as async.
The second GET endpoint /courses/[courseId] is implemented using the controller method getCourse. The @Get method decorator is used. This time the string ‘:courseId’ is passed into the decorator to specify that the GET request is accepting a URL parameter. This URL is passed into the method by adding the courseId parameter to the method declaration and using the @Param decorator.
To retrieve the course dataset for the given id the service method getCourse is used.
The third method which is being implemented in CoursesController is addCourse. This method is handling incoming HTTP Post request for the default controller endpoint /courses. This is being specified by using the @Post() decorator in front of the method declaration.
As the HTTP Post request to that endpoint should be used to create a new course dataset the data of the new course is passed in the request body. To get access to the request body a method parameter is introduced and the parameter decorator @Body() is used. In order to define the structure of the body data a so called Data Transfer Object (DTO) type is used: CreateCourseDto. This custom class is implemented in file create-course.dto.ts:
export class CreateCourseDto {
readonly id: number;
readonly title: string;
readonly description: string;
readonly author: string;
readonly url: string;
}
This is a class just consisting of the five courses properties.
As the body parameter createCourseDto of method addCourse is then passed into the call of service method addCourse diectly.
Last controller method which is being implemented is handling the HTTP DELETE request and is named deleteCourse. To indicate that this method is handling an DELETE request the @Delete decorator is added. The identifier of course which needs to be deleted is passed via query parameter. Here is an example for the delete URL for course with identifier 1:
https://localhost:3000/courses?courseId=1As you can see the query Parameter is added by attaching a question mark at the end of the URL following by the value assignment to the parameter. To get access to query parameters a parameter is added to the method definition. The @Query decorator is used for this query parameter and the value of courseId can be accessed by query.courseId
.
Testing The New Endpoint With Postman
Now, we’ve added to code which is needed to full-fill the requirements. Let’s test out the API by using a tool called Postman. You can download a free version of Postman at https://www.getpostman.com/ .
First let’s try out to retrieve the complete list of courses by executing an HTTP GET request for endpoint http://localhost:3000/courses
The result we’re getting back is the list of courses in JSON format. Next let’s just retrieve one single course by using the identifier:
Again the result is returned in JSON format. This time the result is just consisting of one course dataset.
Next we need to test to send a POST request to create a new course. In Postman you can use the Body tab to enter the course data in a table (by using the x-www-form-urlencoded option):
Sending out this request created a new course and as a response you get back the newly created dataset in JSON format.
Finally let’s try to delete a course by sending an HTTP DELETE request to _ http://localhost:3000/courses/courseId=3_ Here we’re using the courseId query Parameter to specify the identifier of the course which should be removed.