NestJS + Prisma + Typescript = Robust, and Powerful GraphQL APIs in less than a day.
Code: https://github.com/karthickthankyou/nestjs-prisma-simple-graphql-api
Getting started
// Initialize a new nest application
npx nest new graphql-api --package-manager=yarn
// Install dependencies
yarn add graphql @apollo/server @nestjs/graphql @nestjs/apollo
Add Graphql module in Nestjs
GraphQLModule.forRoot<ApolloDriverConfig>({ // Configuring the GraphQL module using the Apollo driver with a specific configuration type
driver: ApolloDriver, // Specifying the Apollo driver to be used for GraphQL
fieldResolverEnhancers: ['guards'], // Applying guards as field resolver enhancers, providing additional control and security
autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // Automatically generating the schema file from types and resolvers, saving it in the specified path
introspection: true, // Enabling introspection, allowing tools like GraphQL Playground to fetch the schema
}),
Using the Apollo driver, this configuration provides a solid foundation for a GraphQL server in NestJS. It includes automatic schema generation, field-level security through guards, and introspection for development tooling.
Setup env Config
yarn add @nestjs/config
ConfigModule.forRoot(),
The @Module decorator for the AppModule looks like the one below.
@Module({
imports: [
ConfigModule.forRoot(),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
fieldResolverEnhancers: ['guards'],
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
introspection: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {}
Enable nest-cli plugin
The plugin simplifies the development process when working with GraphQL in NestJS, providing automation and addressing some of TypeScript’s limitations. It aids in structuring the code efficiently, making it easier to define and maintain GraphQL schemas.
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"plugins": [{ "name": "@nestjs/graphql" }] // Add this.
}
}
Read more about this here.
The test database
Let's have a Postgres database running locally using docker.
version: '3.8'
services:
db:
container_name: autospace_db
image: postgres
restart: always
ports:
- 1000:5432
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
pgadmin:
container_name: autospace_pgadmin
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
ports:
- '1100:80'
depends_on:
- db
volumes:
db_data:
Add the environment variables POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD, PGADMIN_EMAIL, and PGADMIN_PASSWORD
in a .env in the same location as the docker-compose.yml file.
Make sure you have the docker desktop installed and running. Run the database using the below command.
docker compose up -d
Initialize Prisma
yarn add prisma -D
yarn prisma init
Modify the DATABASE_URL
in the .env file.
Design Prisma Schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
uid String @id
createdAt DateTime @default(now())
name String? // Nullable field
posts Post[] // One-to-many relation with Post
comments Comment[] // One-to-many relation with Comment
}
model Post {
id Int @id @default(autoincrement())
title String
body String
published Boolean @default(false) // With default value
author User @relation(fields: [authorId], references: [uid]) // Many-to-one relation with User
authorId String
comments Comment[] // One-to-many relation with Comment
}
model Comment {
id Int @id @default(autoincrement())
text String
post Post @relation(fields: [postId], references: [id]) // Many-to-one relation with Post
postId Int
user User @relation(fields: [userId], references: [uid]) // Many-to-one relation with User
userId String
}
The yarn prisma migrate dev
command is used in development to create and apply database migrations based on changes to the Prisma schema. It automates the database schema update, generates the Prisma Client to match the schema, records a migration history, handles potentially destructive changes with interactive prompts, and utilizes a shadow database for safe testing. This command streamlines database iteration and client generation in the development environment, making it an essential tool for managing schema changes.
yarn prisma migrate dev
Prisma module
Refer to the documentation to create a Nest module for Prisma ORM. For our example, create the below files in src/common
folder.
// src/common/prisma/prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect()
}
}
// src/common/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common'
import { PrismaService } from './prisma.service'
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Write Nest Models
Including rich filtering, and sorting capabilities in a Graphql is difficult. There are also commonalities in writing the complex Input and Arg types for the everchanging models.
I have written an npm package for code-generationnestjs-prisma-codegen
. Also, find the GitHub code here.
In your src/ directory, Just run the command with the required model name.
npx nestjs-prisma-codegen User
That will generate all these files.
├── common
│ ├── dtos
│ │ └── common.input.ts
├── models
│ └── users
│ ├── dto
│ │ ├── create-user.input.ts
│ │ ├── find.args.ts
│ │ ├── order-by.args.ts
│ │ ├── update-user.input.ts
│ │ └── where.args.ts
│ ├── entities
│ │ └── user.entity.ts
│ ├── users.module.ts
│ ├── users.resolver.ts
│ └── users.service.ts
We generated only the essential ones. Let's look at the generated entity file.
import { ObjectType } from '@nestjs/graphql'
import { User as UserType } from '@prisma/client'
import { RestrictProperties } from 'src/common/dtos/common.input'
@ObjectType()
export class User implements RestrictProperties<User, UserType> {
uid: string
createdAt: Date
name: string
}
Notice I use a custom type RestrictProperties
that implements the types generated by Prisma so that the Prisma schema and Nestjs types will always be in sync.
export type RestrictProperties<T, U> = {
[K in keyof T]: K extends keyof U ? T[K] : never
} & Required<U>
This will help to avoid adding extra properties or missing the existing ones from the Prisma schema.
Syncing the Nestjs types with the Prisma types will be a breeze after every database migration!
Once you implement the Prisma types to create nest types, You can feel free to add or remove services and resolvers.
Finish the API
Once you are happy with the resolvers and services. Add the modules to the app.module.ts
file.
@Module({
imports: [
ConfigModule.forRoot(),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
fieldResolverEnhancers: ['guards'],
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
buildSchemaOptions: {
numberScalarMode: 'integer',
},
introspection: true,
}),
PrismaModule,
UsersModule,
PostsModule,
CommentsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
yarn start:dev
Your API is ready. 🎉
I prefer using apollo explorer for exploring the APIs as it requires much less typing.
Link: https://studio.apollographql.com/sandbox/explorer
Enable Cors
The Graphql API we created is restricted to access from any other domain by default. Update the main.ts with the app.enableCors() adding ALLOWED_ORIGINS
from the .env
file.
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',')
console.log(allowedOrigins)
app.enableCors({
origin: allowedOrigins,
allowedHeaders: '*',
methods: '*',
})
await app.listen(3000)
}
bootstrap()
Sample .env
file
ALLOWED_ORIGINS=https://studio.apollographql.com,https://autospace.iamkarthick.com,http://localhost:3001
The in-depth argument types
Graphql is not just about underfetching and overfetching. The nested arg types give us a crazy amount of flexibility.
Look at a sample OrderBy arg type.
@InputType()
export class CommentOrderByWithRelationInput
implements
RestrictProperties<
CommentOrderByWithRelationInput,
Prisma.CommentOrderByWithRelationInput
>
{
@Field(() => Prisma.SortOrder, { nullable: true })
id: Prisma.SortOrder
@Field(() => Prisma.SortOrder, { nullable: true })
text: Prisma.SortOrder
@Field(() => Prisma.SortOrder, { nullable: true })
postId: Prisma.SortOrder
@Field(() => Prisma.SortOrder, { nullable: true })
userId: Prisma.SortOrder
@Field(() => PostOrderByWithRelationInput, { nullable: true })
post: PostOrderByWithRelationInput
@Field(() => UserOrderByWithRelationInput, { nullable: true })
user: UserOrderByWithRelationInput
}
Remember, the nested input types PostOrderByWithRelationInput
and UserOrderByWithRelationInput
are generated by nestjs-prisma-codegen
.
Resolve Fields
Let’s also satisfy the fetch-it-all-together facility Graphql API provides using ResolveField
directive.
Inject the PrismaService in the constructor and we can use the powerful Prisma API to return information based on the parent data.
In this example, we return the author
and comments
that belong to the post.
constructor(
private readonly postsService: PostsService,
private readonly prisma: PrismaService,
) {}
// ...
@ResolveField(() => User)
author(@Parent() parent: Post) {
return this.prisma.user.findUnique({ where: { uid: parent.authorId } })
}
@ResolveField(() => [Comment])
comments(@Parent() parent: Post) {
return this.prisma.comment.findMany({ where: { postId: parent.id } })
}
Demo!
Our API has
- Mutations with validated Data Transfer Objects.
- Rich argument types with filtering, sorting, and pagination capabilities.
- Resolve fields to fetch related data together.
Next time you come across a Graphql API tutorial, notice how extensive the filtering and sorting capabilities of that. 90% of the tutorials don't go beyond implementing skip
and limit
arguments.
Thanks for reading.
Code: https://github.com/karthickthankyou/nestjs-prisma-simple-graphql-api
Happy coding. 🎉