Building a REST API with NestJS and Prisma ORM

Teten Nugraha
12 min readOct 5, 2022

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:

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.

  1. What name would you like to use for this resource (plural, e.g., “users”)? articles
  2. What transport layer do you use? REST API
  3. 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.

--

--

Teten Nugraha

Software Engineer, 8 years of experience. Expertise with Microservices, Spring Boot, CICD, Docker https://www.linkedin.com/in/teten-nugraha