NestJS GraphQL/Prisma/PostgreSQL Set Up

Seunghyun Kim
5 min readAug 2, 2023

--

You can see the whole code on the graphql-prisma-postgresql branch.

To develop the business logic mentioned in the last article, we will have to define the data model. Prisma will be used for the purpose since it is intuitive and type-safe. After defining the model, we will migrate it to a database we want to use. And then, we will have a GraphQL server that are responsible for handling requests and responses between clients and our database.

Prisma

First, we will create Prisma schema file using the commands shown below.

npm install prisma
npx prisma init

I defined the data model for the project as shown below. Here, we have Toilet, Address and Review tables in our database. Notice Toilet has some relation to both Address and Review.

model Toilet {
id String @id @default(uuid())
address Address?
reviews Review[]
isPaid Boolean
price Int
}

model Address {
id String @id @default(uuid())
streetNumber String
street String
city String
country String
toiletId String @unique
toilet Toilet? @relation(fields: [toiletId], references: [id])
}

model Review {
id String @id @default(uuid())
cleanliness Float
performance Float
description String? @db.VarChar(1024)
contributedBy String @unique
toiletId String
toilet Toilet? @relation(fields: [toiletId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Migrate to PostgrSQL

We will set up PostgreSQL database on our local machine for development using Docker. Even though this is a local development, we will use environment varaibales to run the database as if we deployed it on production.

To begin to set it up, add the following variables in .env file:

POSTGRES_HOST='localhost'
POSTGRES_PORT=5432
POSTGRES_USER='your-user-name'
POSTGRES_PASSWORD='your-password'
POSTGRES_NAME='your-postgres-name'

Add the following code in docker-compose.yml:

services:
postgres:
image: postgres
restart: always
env_file:
- .env
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_NAME=${POSTGRES_NAME}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_HOST=${POSTGRES_HOST}
volumes:
- postgres:/var/lib/postgresql/data
ports:
- '5432:5432'
volumes:
postgres:

And run the PostgreSQL by entering:

docker-compose up -d

We can check if the database container is running or not with Docker Desktop or VScode extension.

Now, let’s migrate the data model to the database:

npx prisma migrate dev --name [whatever-you-want]

Once the migration is done, check if all the tables are created in the database, run a docker command like:

docker exec -it [your-container-name] psql -U [your-user-name] [your-postgres-name]
// And then, run the following query
\dt

It will show all the tables we have defined in Prisma schema file.

GraphQL

GraphQL is a query language for APIs. Unlike REST API, we do not have to retrieve data that we do not need. In other words, we can specify fields that we want to get as a response from an API based on our needs.

Conveniently, there is the built-in @nestjs/graphql module in NestJS. Let's install the package and other necessary ones:

npm i @nestjs/graphql @nestjs/apollo @apollo/server

We need a module that handles requests and response about toilet data. Let’s create the following files in the toilet folder under the src folder:

src
└── toilets
├── dto
│ ├── args
│ │ ├── get-toilet.args.ts
│ │ └── get-toilets.args.ts
│ └── input
│ ├── create-toilet.input.ts
│ ├── delete-toilet.input.ts
│ └── update-toilet.input.ts
├── models
│ └── toilets.ts
├── toilets.module.ts
├── toilets.repository.ts
├── toilets.resolver.ts
└── toilets.service.ts

Instead of explaining all the codes in each file, we will focus on what a few of important files do.

  • DTO stands for Data Transfer Object. We specify data types and fields that we want the GraphQL server to get from clients in the dto folder.
  • We define the Toilet model to let the GraphQL know types and fields we need in the model folder.
  • We define a controller layer that controls a GraphQL operation such as query, mutation and subscription in the resolver file.
  • We define a repostory layer that does CRUD operations to our database in the repository file.
  • We define a service layer that provides services added to it such as adding long-time operation to a queue in the service file.

Note: I took the code first approach — The opposite is the schema first approach. For the code first approach, we should annotate classes with proper decorators provided by @nestjs/graphql.

We will create address and review module in the same structure as the toilet module.

Only thing that I was struggling with here is how to resolve relations each table of the database has. Recall that Toilet table has relations with Address and Review tables. To let the GraphQL be able to resolve these relations, I did like in the toilet resolver:

//..
@Resolver(() => Toilet)
export class ToiletsResolver {
constructor(
private readonly toiletService: ToiletsService,
private readonly addressService: AddressService,
private readonly reviewsService: ReviewsService,
) {}
//.. check out my git repo for the whole code

@ResolveField('address', () => Address)
async getAddressByToiletId(@Parent() toilet: Toilet): Promise<Address> {
return this.addressService.getAddress({ toiletId: toilet.id });
}

@ResolveField('reviews', () => [Review])
async getAllReviewsByToiletId(@Parent() toilet: Toilet): Promise<Review[]> {
return this.reviewsService.getReviews({ toiletId: toilet.id });
}
}

I brought Address/Review services and create methods with ResolveField decorator to solve the problem.

Now, let's make some requests from GraphQL playground to simulate our app.

Test Requests

Create Toilet

Create Address

Create Review

Get Toilet

Thoughts

NestJS is well-structured and easy for us to modularize a certain service by its nature. With that fact in mind, I think I might be going to make our NestJS app a few of microservices combined later in the future.

You can see the whole code on the graphql-prisma-postgresql branch.

THANKS FOR READING. SEE YOU NEXT TIME!

This article is originally posted on my own blog:

--

--

Seunghyun Kim

Software Developer | Beginner Writer | Language and Culture Enthusiast - https://shkim04.github.io/en