Building a REST API with NestJS and Prisma ORM
NestJS is one of the frameworks built on the NodeJS platform, which NodeJS programmers have widely used. In this tutorial, I will share how easy it is to build a REST API with NestJS.
Introduction
In this material, you will be guided step by step to build a Median system. First, we will start creating this project by creating a NestJS project and then proceed with the PostgreSQL DB step, which will later be integrated with Prisma ORM. And I hope this article will help you find out how to create a REST API with NestJS so that you will have a sufficient foundation to learn NestJS to a more advanced level in the future.
Stacks
You will be guided to be able to use some of the tools below:
- NestJS as backend framework
- Prisma as Object Relational Mapping (ORM)
- Postgresql as Database
- Swagger as API dokumentasi
- and sure, Typescript as programming language
Prerequisites
This is a tutorial for beginners. But in this tutorial, I’m assuming that you’ve already:
- Basic knowledge of Javascript or Typescript (preferred)
- Know the basics of NestJS
If you don’t know an overview of NestJS, read the NestJS Overview before starting to try this article.
Development Environtment
but before that, make sure that on your local laptop you have the tools installed as below:
Create a NestJs project
Before creating a NestJS project, you must first install the Nest CLI. You can create projects, resources, controllers, services, and more with the Nest CLI. To get started, type the following command
npx i -g @nestjs/cli new median
The CLI will ask you to choose a package manager for this project — choose npm. Then you will get a NestJS project with the median folder name. Next, open the visual studio code and open the project. You will get a project structure more or less like this.
median
├── node_modules
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
But we will focus on the src folder where the folder is where we write the code. NestCLI has done a project for you; there are a few things that need to be underlined are:
- src/app.module.ts as root module from application.
- src/app.controller.ts as a basic response from Controller that will be produce the output like “Hello World”.
- src/main.ts main entry from application.
You can run the project using the following command:
npm run start:dev
This command will use watch mode, where every code change will immediately trigger a restart of the application. To check whether the application is running, open a browser and enter the address localhost:3000; you will see a blank page with the words Hello World.
You must keep the application / server running while following this tutorial.
Create Database in Postgresql
The next step is to create a database in Postgresql; for example, with the name median-DB, you can use DBeaver or other DB Tools.
The database has been created, then the next step is to setup Prisma ORM.
Setup Prisma ORM
Initialize Prisma
To be able to use Prisma, all you have to do is install Prisma CLI. By using Prisma CLI, you can give commands related to the database in your application.
npm install -D prisma
Once you’ve finished downloading the Prisma library, you can initialize prima into your project using the command.
npx prisma init
This command will generate a folder with the name Prisma a schema.prisma file. In addition, Prisma will also create a file called .env at the root of the project folder.
Setup Environtment Variable
In the .env file, you will see a property named DATABASE_URL with a dummy value. Replace it with your username, password, and database name on your localhost PostgreSQL.
// .env
DATABASE_URL="postgres://username:password@localhost:5432/median-db"
Please review the postgresql username and password and enter it into the .env file above.
Memahami Schema Prisma
Suppose you open the prisma/schema.prisma file you will see the default schema as follows:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
This file is written using Prisma Schema Language, where with this language, Prisma can easily define schema quickly. schema.prisma is divided into three main components, namely:
- Datasource: Defines the connection to the database. The .env configuration above states that the database provider you are using is PostgreSQL and the address that connects the application to the database is in the DATABASE_URL variable.
- Generator : Indicates that you agree to generate a prism client that can be used to send queries to the database.
- Data Model: Defines your database model. Each model is mapped into a table that resides in a predefined database. That’s right; for now, you haven’t seen any model in the prism schema; you will explore that in the next part.
If you want to explore Prisma more deeply, you can visit the prisma docs link
Create Article Model
Now it’s time to define the model of your application. In this tutorial, you only need an article model to represent each article in the blog. In the prisma/prisma.schema file, create a new model with the name Article.
// prisma/schema.prisma
model Article {
id Int @id @default(autoincrement())
title String @unique
description String?
body String
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Here you have created an Article model with several fields. A field has a name (id, title, description) and a type (Int, String, etc.). And some optional attributes like @id, @unique, etc. A field can be optional by adding a ? in addition to the data type.
The id field has a particular @id attribute. This attribute indicates that this field is the primary key of the model, and the @default(autoincrement) attribute also indicates that this field will be filled with an auto-increment value each time you add new data.
The published field is a flag that determines whether this article is published or still in draft form. The @default(false) attribute indicates that this field will be set to false by default.
The two DateTime fields createdAt and updatedAt will keep track of when the article was created and updated. In addition, the @updatedAt field will automatically update its value with the current timestamp value wherever the article is updated.
Migrate Database
After defining the prism schema and model, the next step is to run the migrate command to convert the model into a table form in the PostgreSQL database.
npx prisma migrate dev --name "init_article_table"
The command above will do the following three things:
- Save the migration: Prisma will take a snapshot of the schema and find out which SQL commands are suitable for this migration process. Here prisma will also save the migrations file containing SQL commands in the prisma/migrations folder.
- Execute the migration: Prisma will perform the migration process from the migrations file into a table in the database.
- Generate Prisma Client: Prisma will generate Prisma Client based on the last schema. You can see the @prisma/client library in package.json. Prisma Client is a library that uses typescript whose job is to generate automatic prismatic schema. Besides, applications can communicate with the database using this operation, such as adding, updating, deleting, and searching processes.
Note: For more details, you can visit the prism client documentation here.
If the migration process is successful, the output will be something like this
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20220528101323_init_article_table/
└─ migration.sql
Your database is now in sync with your schema.
...
✔ Generated Prisma Client (3.14.0 | library) to ./node_modules/@prisma/client in 31ms
Then you can also check, Prisma has created a SQL script, which is to create an article table.
-- prisma/migrations/20220528101323_init/migration.sql
-- CreateTable
CREATE TABLE "Article" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"body" TEXT NOT NULL,
"published" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);-- CreateIndex
CREATE UNIQUE INDEX "Article_title_key" ON "Article"("title");
Create Prisma Service
In NestJS applications, it is a good best practice to abstract the prism of client APIs. To do this, you can create a service with the name Prisma Service which is in charge of initiating the Prisma Client and bridging between the application and the database. NestJS provides easy steps to create a prism service, type the following command.
npx nest generate module prisma
npx nest generate service prisma
The above command will create a new subdirectory ./src/prisma containing the prisma.module.ts and prisma.service.ts files.
Update prisma.service.ts file to be like this
// src/prisma/prisma.service.ts
import { INestApplication, Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
function enable Shutdown Hooks ensures the application can gracefully shutdown.
Note: Graceful shutdown is a mechanism that allows us to run several procedures before the shutdown procedure is executed so that the application or system can minimize process loss in the middle of the road
The Prisma Module is responsible for creating a singleton from the Prism Service and allows sharing the Prism Service to each module in the application. To do this, you must add PrismaService to the exports array in the prisma.module.ts file.
// src/prisma/prisma.module.ts
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Now, any module in the application can use PrismaService and be injected into any component/service. This is the common pattern in the NestJS framework. Prisma setup is done, the next thing is to set up swagger for Documentation API.
Setup Swagger API Doc
Swagger is an API Documentation tool that uses the OpenAPI Specification. Luckily, NestJS has wrapped it in its own package. To be able to install it use this command
npm install --save @nestjs/swagger swagger-ui-express
Now open main.ts file and initiate Swagger using SwaggerModule
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
As long as the application is running, then open this address localhost:3000/api. you will see that the swagger display can be used.
CRUD Articles
In this session, we will create a create, read, update, delete feature for Article resources.
Generate REST Resource
NestJS makes it very easy for developers to create resources using REST. You will be asked to fill in some data using the Nest CLI below.
npx nest generate resource
A CLI prompt will appear, and you will be asked to select it.
- What name would you like to use for this resource (plural, e.g., “users”)? articles
- What transport layer do you use? REST API
- Would you like to generate CRUD entry points? Yes
You’ll also find a new src/articles directory with all the files you need to build a REST endpoint. The src/articles/articles.controller.ts file contains a route handler that holds requests and passes responses from different endpoints. While the file src/articles/articles.service.ts, which we will fill later with the logic of this article’s endpoint.
The resource articles will appear if you go to the Swagger API page again.
SwaggerModule reads all @Body(), @Query() and @Param() decorators in the controller to generate the API page.
Adding PrismaModule into Articles module
To use PrismaClient in the Article module, you must add PrismaModule in the imports section of the ArticleModule file.
// src/articles/articles.module.ts
import { Module } from '@nestjs/common';
import { ArticlesService } from './articles.service';
import { ArticlesController } from './articles.controller';
import { PrismaModule } from 'src/prisma/prisma.module';
@Module({
controllers: [ArticlesController],
providers: [ArticlesService],
imports: [PrismaModule],
})
export class ArticlesModule {}
After that you can inject PrismaService in ArticlesService.
// src/articles/articles.service.ts
import { Injectable } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}
// CRUD operations
}
Create GET /articles endpoint
Still in the src/articles/articles.controller.ts file the findAll() method will return all the articles in the database.
// src/articles/articles.controller.ts
@Get()
findAll() {
return this.articlesService.findAll();
}
You should also update the ArticlesService.findAll() file so that it can retrieve data from the database and pass it to the controller.
// src/articles/articles.service.ts
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}
create(createArticleDto: CreateArticleDto) {
return 'This action adds a new article';
}
findAll() {
return this.prisma.article.findMany({ where: { published: true } });
}
The findMany query will return all records in the database that match the given where condition.
Now you can test the endpoint, access localhost:3000/api and click on the GET/articles dropdown menu. Press try it out then execute
Note: you can also test it using postman or insomnia tools.
Create GET /articles/drafts endpoint
You will create an endpoint that returns all unpublished (draft) articles. For this part, NestJS will not create it automatically and must write it manually.
/ src/articles/articles.controller.ts
@Controller('articles')
export class ArticlesController {
constructor(private readonly articlesService: ArticlesService) {}
...
@Get('drafts')
findDrafts() {
return this.articlesService.findDrafts();
}
}
Your editor will display an error message because the this.articlesService.findDrafts() function does not exist. For that, open the article service file and add the findDrafts() function.
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}
...
findDrafts() {
return this.prisma.article.findMany({ where: { published: false } });
}
}
Then you can try it yourself in the Swagger API Doc.
Create GET /articles/:id endpoint
// src/articles/articles.controller.ts
@Get(':id')
findOne(@Param('id') id: string) {
return this.articlesService.findOne(+id);
}
This route accepts a dynamic id as a parameter, which will be passed to the findOne() function. However, in the findOne() function, the id must use the number type; for that, before being passed to the function, the id is added with a plus sign (+ ) which functions to change (casting) from string to number.
Then add the findOne() function inside the article service
findOne(id: number) {
return this.prisma.article.findUnique({ where: { id } });
}
You can test using the swagger doc by accessing the GET endpoint /articles/{id}
Create POST /articles endpoint
// src/articles/articles.controller.ts
@Post()
create(@Body() createArticleDto: CreateArticleDto) {
return this.articlesService.create(createArticleDto);
}
Note that this endpoint requires the CreateArticleDto class as its payload. For that, create an empty class with the name CreateArticleDto.
// src/articles/dto/create-article.dto.ts
import { ApiProperty } from '@nestjs/swagger';
export class CreateArticleDto {
@ApiProperty()
title: string;
@ApiProperty({ required: false })
description?: string;
@ApiProperty()
body: string;
@ApiProperty({ required: false, default: false })
published?: boolean = false;
}
The @ApiProperty decorator makes the variables in a class visible and readable by the SwaggerModule. For more details, you can read the documentation directly.
Next update the article service and add a new update function like this
// src/articles/articles.service.ts
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {
}
...
create(createArticleDto: CreateArticleDto) {
return this.prisma.article.create({ data: createArticleDto });
}
// ...
}
Create PATCH /articles/:id endpoint
This endpoint is used to update article data stored in the database. This endpoint is as below.
// src/articles/articles.controller.ts
@Patch(':id')
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articlesService.update(+id, updateArticleDto);
}
this endpoint uses updateArticleDto as its payload and updateArticleDto is a PartialType of CreateArticleDto so updateArticleDto can access all properties in CreateArticleDto;
// src/articles/dto/update-article.dto.tsimport { PartialType } from '@nestjs/swagger';
import { CreateArticleDto } from './create-article.dto';export class UpdateArticleDto extends PartialType(CreateArticleDto) {}
As before, you must update the article service for this update operation
// src/articles/articles.service.ts
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}
// ...
update(id: number, updateArticleDto: UpdateArticleDto) {
return this.prisma.article.update({
where: { id },
data: updateArticleDto,
});}
// ...
}
The article.update operation will start searching for the article with the given id and the data will be updated based on the data given from updateArticleDto.
If there is no article searched by id, then Prisma will throw an error.
Create DELETE /articles/:id endpoint
Like the previous step, you will edit the article service in the delete function like this
// src/articles/articles.service.ts
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) { }
// ...
remove(id: number) {
return this.prisma.article.delete({ where: { id } });
}
}
Congratulations, this is the last step, if you follow this article, it means that you have got basic knowledge about NestJS. In the next step, we will try to create the authentication and authorization functions. Stay on standby on this blog, coding friends….
If it was interesting or helpful to you, please do press the 👏 clap button and help others find this story too or if you wanna talk internally with me , reach me in https://linktr.ee/teten_nugraha.
I’m taking part time job too, if you’re interested let’s chat personally.