Next x Nest – GraphQL for REST API developers - Part 1
Tags: webdev, graphql, nestjs, tutorial
Published: 2023-10-06 18:30:00

In the past articles in this series, we have explored the frontend and backend side of authentication. If you have been following those, then right about now you’d also have a fully functional frontend in  NextJS which uses Next Auth to facilitate Authentication with Google OAuth 2.0 and communicates with a backend built using NestJS which also has a separate but interoperable authentication system using Passport and Google OAuth.

At this stage, we are ready to flesh out our backend. As informed previously, we will be building our backend in GraphQL. So, even though the authN was done with REST APIs, the remainder of the backend will be exposed as GraphQL API. Why was this design decision made? Well, GraphQL allows for flexibility and customization on the frontend. It is one of those things that almost every seasoned backend dev is aware of and likes but tries to sometimes avoid. Since I have a knack for article articles on such things which are often “avoided” and because the Kanban board we are building will require this level of flexibility, the choice of GraphQL with Nest was made.

Just to confirm again, we will be using NestJS to make our lives easier. This article will be aimed at devs who have a good understanding of REST APIs and a basic understanding of NestJS. Keep in mind that NestJS offers 2 ways of using GraphQL
1.	Code-first Approach – Here you use the **entities** in NestJS to create the schema in a format that would be familiar to someone who has written DTOs in NestJS. NestJS takes these **entities** and stitches a GraphQL schema. The developer need not really worry about touch Schema Definition Language.
2.	Schema-first Approach – Here you specify the Schema in individual modules first. NestJS then combines these schemas and creates a singular schema definition language file. From this file, NestJS then generates types and entities to be used in the project.

There is a third approach where you can use both paradigms at once in the same project but it is complicated and can lead to exposure of multiple GraphQL endpoints and redundancies in schema definition if not used in a microservice-based architecture. We will explore this paradigm as well once we have explored the above two.

In this article, we will explore the “Code-first Approach”. This is geared towards REST API devs and makes them feel more at home. Apart from this, we will also explore how to use 2 different databases with Prisma in the same NestJS project and manage things like migrations and Prisma Studio between them.

## Housekeeping
Before we begin, you will need to provision a new PostgreSQL (different from the Supabase one we have been using thus far). For this tutorial, we will use Render. Render offers free managed PostgreSQL instances for a period of 90 days. If you haven’t created a Render account yet, head over and sign up. You will be redirected to your Render Dashboard. Click on “New” in the Appbar and select PostgreSQL DB. You will see something similar to the image shown below. Fill in the fields and click on Create.


![Render Postgres Isntance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40qt4isz3ci5ma2e57ei.png)

Once the creation is complete, move over to the **Info** tab of your instance. Here you will get the External Connection URL. As expected, this will be used to connect from your NestJS project using Prisma. Also, make sure that the **Access Control** is set to `0.0.0.0/0` as shown in the below image. This means anyone on the internet with the Connection string will be able to access the DB instance. This is why it's paramount to keep your connection string a secret. Storing it in a `.env` file is a step in that direction.


![Render Postgres Instance Info](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqf6tla6fhs3cqlmy7qx.png)

To use GraphQL in a NestJs project we need to install NestJs’ Apollo and GraphQL packages. For these, run `npx i @nestjs/apollo @nestjs/graphql`. Once this finishes, move over to your `app.module.ts`. Here we will import the GraphQL config module and make it generally available. To do this, inside the `imports` array, add the following:

```typescript
// Add the following to the imports array
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      include: [CardModule, BoardModule, BoardUserModule, ColumnModule],
      autoSchemaFile: join(process.cwd(), 'src/graphql-schema/schema.gql'),
    }),
```

## Using Multiple Prisma Schema in a single project
We have set up a Supabase instance and connected it to our NestJS project. That will be used only for Auth. For all other purposes, we will use another PostgreSQL instance. This also means we will have 2 separate `schema.prisma` files and Prisma clients.

[This](https://dev.to/micalevisk/nestjs-tip-how-to-inject-multiple-versions-of-the-same-provider-into-one-module-eg-many-axios-instances-5agc) article by @micalevisk offers great insight into how to use different versions of the same module in a NestJS project. In our case, we will be using different Prisma Client Modules in the same Nest project. Remove traces of the Prisma module we developed in our last article from our project and then run the following commands:

1.	`nest new module prisma-render` followed by `nest new service prisma-render` - This will create the first Prisma Module which we will use to connect to our Render Postgres DB instance.
2.	`nest new module prisma-supabase` followed by `nest new service prisma-supabase` - This will create the Module which we will use to connect to our Supabase Postgres instance.

### Prisma Supabase Module
Our `PrismaSupabaseModule` will be a global module and it will export the service of the module. This will enable other module like `AuthModule` and `UserModule` to utilize it with ease. The code below for our `prisma-supabase.module.ts` file does exactly that:

```typescript
// prisma-supabase.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaSupabaseService } from './prisma-supabase.service';

@Global()
@Module({
  providers: [PrismaSupabaseService],
  exports: [PrismaSupabaseService]
})
export class PrismaSupabaseModule {}

```
Before moving to the service, we need to make certain changes to our schema file. Firstly, we need to rename it. As a good practice, we will call our Supabase schema file `supabase.schema.prisma`. We need not touch the schema definition in the file. Just a little change in the generator configuration and we are daisy!


![Val Kilmer Daisy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f1lvnqavesly8u0lvsgl.gif)

Our dependencies are being taken care of by Turbo and the NestJS & NextJS dependencies are both stored globally. The client our Prisma will generate will also need to be put in that location for easy path resolution. For that reason, we will need to change the `output` path from default to inside a special folder in the `node_modules` as shown below.

```
// generator for supabase.schema.prisma
generator client {
  provider = "prisma-client-js"
  output = "../../../node_modules/@prisma/supabase"
}
```

Now with this change we need to run `npx prisma generate –schema=./prisma/supabase.schema.prisma`. Note how we specify the name of the schema file since there is no default `schema.prisma` file for Prisma to pick up automatically. This will generate our Supabase Prisma Client inside the Prisma dependency of the global `node_modules`. We can now move to create our Supabase service.

Inside our `prisma-supabase.service.ts` file of our `prisma-supabase` module, we will need to import the `PrismaClient` from `@prisma/supabase` instead of our default `@prisma/client`. This is because our Supabase-specific Prisma client is now generated inside our custom location. The remainder of the code will remain similar to the steps we took in our previous article when we created our Prisma module. So hopefully the code below for our `prisma-supabase.service.ts` needs no further explanations.

```typescript
// prisma-supabase.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '@prisma/supabase'; //NOTE HERE

@Injectable()
export class PrismaSupabaseService extends PrismaClient {
  constructor(configService: ConfigService) {
    super({
      datasources: {
        db: {
          url: configService.get('SUPABASE_DATABASE_URL'),
        },
      },
    });
  }

}

```

Finally, we will change the imports of our `AuthModule` which needs to use this `PrismaSupabaseModule`. Inside the `auth.service.ts` we will change to `PrismaSupabaseService` instead of our old `PrismaService` (which no longer exists). The minor changes are shown below:

```typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { GoogleLoginUserDto } from './dto/google-login.dto';
import { PrismaSupabaseService } from 'src/prisma-supabase/prisma-supabase.service'; // NOTE HERE

@Injectable()
export class AuthService {
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
    private prisma: PrismaSupabaseService, // NOTE HERE
  ) {}

/*
*
* Remainder of the service remains same
*
/

}
```

### Prisma Render Module
Next up, we will need modifications to our `PrismaRenderModule`. But before that, we need to discuss the Schema for our project. Given below is the **Schema** for our Kanban Board. These are the contents of our `render.schema.prisma` file that we need to create inside our `prisma` folder where our `supabase.schema.prisma` file is stored. Explanations follow.

```typescript
generator client {
  provider = "prisma-client-js"
  output   = "../../../node_modules/@prisma/render"
}

datasource db {
  provider = "postgresql"
  url      = env("RENDER_DATABASE_URL")
}

model Board {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  boardName        String
  boardDescription String
  createdBy        String

  columns   Column[]
  boardUsers BoardUser[]
}

model BoardUser {
  userId String
  board  Board  @relation(fields: [boardId], references: [id], onDelete: Cascade)

  boardId String

  @@unique([userId, boardId])
}

model Column {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  columnName        String
  columnDescription String

  board   Board  @relation(fields: [boardId], references: [id])
  boardId String

  cards Card[]
}

model Card {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  cardNumber      Int
  cardName        String
  cardDescription String
  createdBy       String
  assignedTo      String
  storyPoints     Int
  priority        Priority
  status          Status
  startDate       DateTime
  endDate         DateTime

  comments Comment[]
  column   Column    @relation(fields: [columnId], references: [id])
  columnId String
}

model Comment {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  createdBy String
  body      String
  card      Card   @relation(fields: [cardId], references: [id])
  cardId    String
}

enum Priority {
  CRITICAL
  HIGH
  MEDIUM
  LOW
}

enum Status {
  TODO
  IN_PROGRESS
  DONE
}

```

Just like our Supabase client, we will generate our Render client in a custom location right alongside our Supabase client. For this, we change the `output` location of our generator. We will be connecting to our Render DB instance using the `RENDER_DATABASE_URL` env variable. So, keep in mind to include it in the **.env** file and in the Environment Variable Schema check inside `ConfigModule` imported in `app.module.ts`.

We have 5 models in our Kanban board project:
1.	**`Board`** -  Represents a Kanban board. Will have a name, description a list of users who have access to the board. We also record the creator of the board for admin functions.
2.	**`BoardUser`** - Represents the table which records user access to a specific board. We are not having RBAC here. We assume all users in this table have equal rights and rank lower than the board creator.
3.	**`Column`** - This model represents the columns inside our board. While the default ones a board might have are “Backlog”, “To-Do”, “Under Progress” and “Done”, we are not enforcing them. So, every new board has 0 columns. The users need to create these columns by themselves. A column will have a list of Cards.
4.	**`Card`** - These represent the user stories or tasks. A card will have a globally unique ID, a number that will be unique in a board, name, description, story points, assigned to user ID, start & end date, a status (enum which can be `TODO`, `IN_PROGRESS` and `DONE`) and a priority (enum which can be `CRITICAL`, `HIGH`, `MEDIUM` and `LOW`). A card will have a list of comments.
5.	**`Comment`** - Comments that a board user might leave on a card. This can range from a PR created for the tasks to any notes for other users. A Comment will always have a corresponding Card.

Keep in mind that we are not going full gaga over the functionality. The aim of this mini-project is to demonstrate the various moving parts in a Nest + Next project ecosystem. As such, if you want you may include your touches to the mini-project.

Now we will not be running the generate command for Prisma this time around. Here, we have created our schema but our Render Postgres Instance does not contain that. We are going to migrate it. To migrate to a specific DB when you are using multiple Prisma Schema files, we need to specify the schema file to use for the migration using the `--schema` flag. In our case, we need to run `npx prisma migrate dev –schema=./prisma/render.schema.prisma`. This will run the migration and generate the client in the custom location we have specified in the above schema.

Finally, inside `prisma-render.service.ts`, we need to follow the steps we have done for our Supabase client and import the newly generated Render Prisma Client. The code below exactly does that. 

```typescript
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '@prisma/render'; // NOTE HERE

@Injectable()
export class PrismaRenderService extends PrismaClient {
  constructor(configService: ConfigService) {
    super({
      datasources: {
        db: {
          url: configService.get('RENDER_DATABASE_URL'),
        },
      },
    });
  }

}

```

## GraphQL Resolvers
Now with the schema above, you’d expect to create separate modules and write resolvers. In fact, that’s what we need to do. But for the sake of keeping the article short, I am going to cover the 3 more important types of resolvers. The remainder will be inside the GitHub repo on the `main` branch.

Resolvers in GraphQL are your REST API endpoint counterpart. There are no “GET”, “POST”, “PUT” or “DELETE” verbs to work with in GraphQL. You create “resolvers” for your data model (basically those tables or mongodb collections). These resolvers are then invoked from the frontend. Inside the resolvers, you specify which attributes (table column or document field in case of mongodb) of the model you want to return while coding. Typically, all the attributes that may be required are sent as response of a resolver. On the frontend, a query is written using the resolver which basically tells your GraphQL API which parts or attributes from the resolver response the frontend wants. Only those attributes are sent back. That’s how the whole system works.

We will cover the `UserModule`, `BoardModule` and `BoardUserModule`. Between these three, most of the important points can be covered. Before we get started, let’s generate the three modules. Consecutively run the following. Keep in mind that you need to tell NestJs to generate CRUD endpoints and when asked, select **“GraphQL (Code First)”**.
1.	`nest g resource user` - This will generate our `UserModule`. This module will use the Supabase Prisma Client and act as common ground for resolving user info from other resolvers.
2.	`nest g resource board` - This will generate our `BoardModule`. This module will use Render Prisma Client under the hood.
3.	`nest g resource board-user` - This will generate our `BoardUserModule`. While this will primarily use the Render Prisma Client, it will use the `UserModule` to resolve user details of a board user.

### Circular Dependency
Circular dependency is a dreaded problem in the programming world. But unfortunately (or fortunately), circular dependencies are quite common in GraphQL. This will be clear as we go on. For now, remember that NestJS offers an easy way out of it using `forwardRef()`.

Basically, you take the `imports` arrays of the two modules having circular dependencies on one another. Inside the `imports` array, you wrap the imported module with `forwardRef()` as shown below. You do this on both modules. This solves the issue. NestJs will now use the ref of the module to solve the circular dependency.

```typescript
// Inside board.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { BoardService } from './board.service';
import { BoardResolver } from './board.resolver';
import { BoardUserModule } from 'src/board-user/board-user.module';
import { UserModule } from 'src/user/user.module';

@Module({
  imports: [
    forwardRef(() => BoardUserModule), //NOTE HERE
    forwardRef(() => UserModule) //NOTE HERE
  ],
  providers: [BoardResolver, BoardService],
  exports: [BoardService],
})
export class BoardModule {}

```

Likewise importing on the other side (import in the BoardUserModule as well):

```typescript
import { Module, forwardRef } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { BoardModule } from 'src/board/board.module';

@Module({
  imports: [forwardRef(() => BoardModule)], // NOTE HERE
  providers: [UserResolver, UserService],
  exports: [UserService],
})
export class UserModule {}

```

### User Module
While you might think the first thing, we need to define in the module is the module file itself, the entity file is more important. There are also the DTOs we need to define in the module as we will see in the later modules. In this module, the User data is just read so we won’t need any DTOs here as there are no creation or updation happening.

#### User Entity
In NestJS, the entity has an `@ObjectType()` decorator. This tells the NestJS GraphQL engine that what it is reading needs to be provided when this entity is “resolved”. The entity itself is represented by a `class` and its properties closely mirror the DB model and parts of the DB model you want to expose (or how you want to expose them). Take for instance the below User Entity class:

```typescript
import { ObjectType, Field, ID, GraphQLISODateTime } from '@nestjs/graphql';
import { Board } from 'src/board/entities/board.entity';

@ObjectType()
export class User {
  @Field(() => ID)
  id: string;

  @Field(() => String)
  name: string;

  @Field(() => String)
  email: string;

  @Field(() => GraphQLISODateTime)
  emailVerified: Date;

  @Field(() => String)
  image: string;

  @Field(() => [Board])
  boardsCreated: [Board]
}

```
In the above entity class, we say that `User` has an id. This id is of type string. In GraphQL, `ID` is actually a datatype. Every property in an entity needs to be decorated by the `@Field()` decorator. This `@Field()` decorator tells GraphQL the type of the property and whether it can be nullable or not. By default, nothing is _nullable_.

We say that the user will have a name, email, and an image link. These will be string type (denoted by their type inside the class and inside the `@Field()` decorator). The property will be a `Date` type. GraphQL has an inbuilt type of ` GraphQLISODateTime` to represent such Date formats as shown by the corresponding field decorator.

Lastly, we say that when a user is resolved, it must also show the boards that the user has created with the `boardsCreated` property. Note how we specify another entity as the type inside the corresponding `@Field()` decorator. This is a very handy way to represent the `Board` inside other types.

> Throwback to when I said that the entity object closely mirrors the DB and what/how you want to expose the data.
 
We don’t have a `Board` model in our `User` model. The `User` model is described in our **Supabase** instance while the `Board` model is in our **Render** instance. When we write our business logic using our resolver files and service files, we will create a resolver for the board inside our user module. On the frontend, anyone querying the API will not notice the difference in DB storage. Thus, we have two different Database instances’ data being exposed through GraphQL queries. This will also allow the frontend user to write complicated queries which involve both models.

We can go a few steps further and add fields like `comments`, `cardCreated` and `cardsAssigned`. But the main point here is to help you understand how resolving a field works. Once you understand that, feel free to go wild with it.

#### Module
Our Module here is plain and simple. Since we need to resolve the `boards` field in `User` entity, we need to include `BoardService` which comes from `BoardModule`. But we also need to make sure to avoid Circular dependency. So, `forwardRef()` comes in handy. In the code below, we do exactly that.

```typescript
// user.module.ts

import { Module, forwardRef } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { BoardModule } from 'src/board/board.module';

@Module({
  imports: [forwardRef(() => BoardModule)],
  providers: [UserResolver, UserService],
  exports: [UserService],
})
export class UserModule {}

```

#### Resolver
Now, a resolver is the REST controller counterpart in GraphQL. It stands to reason that it will invoke methods in the service file of the module to handle requests and send back responses. In GraphQL, a “query” is any read-type request, and a “mutation” is a write-type request. So, any creation, updation or deletion would be a “mutation”. We need to define these queries and mutation as shown in the code below. Explanations follow.

```typescript
// user.resolver.ts

import {
  Resolver,
  Query,
  Mutation,
  Args,
  Int,
  ResolveField,
  Parent,
} from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './entities/user.entity';
import { GraphQLUser } from 'src/decorators';
import { Board } from 'src/board/entities/board.entity';
import { BoardService } from 'src/board/board.service';

@Resolver(() => User)
export class UserResolver {
  constructor(
    private readonly userService: UserService,
    private readonly boardService: BoardService,
  ) {}

  @Query(() => [User], { name: 'users' })
  findAll() {
    return this.userService.findAll({});
  }

  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => String }) id: string) {
    return this.userService.findOne(id);
  }

  @Mutation(() => User)
  removeUser(@GraphQLUser('sub') id: string) {
    return this.userService.remove(id);
  }

  @ResolveField('boardsCreated', (returns) => [Board])
  getBoardsCreatedByUser(@Parent() user: User) {
    const { id } = user;
    return this.boardService.findAll({createdBy: id});
  }
}

```

In the constructor at lines 19-20, we use the shorthand to define the services we are going to invoke. We are going to need the underlying `UserService` and the `BoardService` which comes from `BoardModule` which we imported into the module.

The first query that we define at line 23 is the counterpart of **“GET all”** route in a REST API. We say that it will return a list of `User` ’s. The second option to the `@Query()` decorator is the name of the query. So, to get a list of all users, the `users` query will be invoked and the fields required will be passed into it.

Second, we have the **“GET by ID”** REST counterpart GraphQL query at line 28. As you might have guessed, it is supposed to return a single `User`. To get the user, one would have to use the `user(id)` query and pass in the list of attributes required.

We won’t be needing a “PUT” or “PATCH” counterpart here. Will be exploring it in the next module. So, we jump to the first mutation at line 33. Note how we do not pass a `{name: <mutation name>}` here. Keep in mind that we do not have that, NestJS will use the function name as the name of the mutation. The same goes for the queries. This “DELETE by  ID” REST API counterpart tells us that when this mutation is invoked, we will take the user ID (which is in the `sub` field of the JWT) and remove the user.

Lastly, we have the `@ResolveField()` decorator. This is used to resolve any outside fields which may exist in the entity. In our case, that’s the `boardsCreated` field in the `User` entity. This is a powerful feature because now, GraphQL can resolve the board and one can pass in the attributes needed from the boards and get back only those attributes. The `@Parent()` decorator is used to get the user whose created boards we want to access. The `user` argument in the parameter list of the `getBoardsCreatedByUser()` function receives the user details. We have defined a `findAll()` function inside `BoardService` which is then invoked to filter out boards by the `createdBy` field on the `Board` model.

#### Service
We will make short work of our user service. We just need three functions in the service that will invoke Prisma API functions for our models defined in the `supabase.schema.prisma` file. As shown in the code below, we just the `findAll`, `findOne`, and `remove` functions. Note how we import the `PrismaSupabaseService`. We didn’t need to import the `PrismaSupabaseModule` because we had declared it as a global module. 

```typescript
// user.service.ts

import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/supabase';
import { PrismaSupabaseService } from 'src/prisma-supabase/prisma-supabase.service';

@Injectable()
export class UserService {
  constructor(private prisma: PrismaSupabaseService) {}
  findAll(where: Prisma.usersWhereInput) {
    return this.prisma.users.findMany({where});
  }

  findOne(id: string) {
    return this.prisma.users.findUniqueOrThrow({where: {id}});
  }

  remove(id: string) {
    return this.prisma.users.delete({where: {id}});
  }
}

```
In line 8, we define our `findAll()` function. This is a weak wrapper around Prisma’s model-specific `findMany()` function. This is more of a personal bias. This helps in importing this service and invoking the `findAll` with filters which would translate to the `where` clause of the Prisma API. It is a handy trick I like to use.

Next, we have the `findOne()` function which takes in an `id` and either returns the user with that ID or throws an exception. Of course, this translated to an Internal Server Error. So, it is always recommended to have NestJS Global Exception filters in place. I like to throw such an error from the service and have it handled in a custom Prisma Exception NestJS filter.

Lastly, we have the `remove()` function which takes in the `id` and returns the deleted user as a response.
This concludes our service.

## What’s Next?
Now I wanted to cover the board and board user modules in this article itself but that would make it 5k + words. That’s way too long for developers with the attention span of a goldfish. Being someone who has a similar attention span, I would never want to inflict that on a fellow developer. So, we will cover those modules and then some in the next part.

That's why I have to bring this article to an abrupt end. Keep in mind that the code discussed here has been open-sourced at the following repo:
{% embed https://github.com/abhik-99/Trivial-Kanban %}

Until then, keep building awesome stuff and WAGMI!