Nest.js introduces a modern stylish way of building backend applications using Node.js by giving it a proper modular and testable structure out of the box. It also provides Typescript and dependency inject support by default which greatly increases the code quality in our projects.
Today, we are going to take an in-depth look at how we can build a GraphQL server using the Nestjs framework by building a simple database CRUD application using MongoDB.
In addition, you will learn how to validate our request input using validation pipes and other best practices.
So, without wasting any further time, let’s get started.
Table of contents
Open Table of contents
Prerequisites
To follow this blogpost I would recommend to have at minimum a basic knowledge about these technologies and install the listed tools:
- A reasonable knowledge of building applications using Javascript and a basic knowledge of Typescript which you can get by reading this crash course
- Basic knowledge of Nestjs and it’s building blocks which can be found in this article
- Nodejs and MongoDB installation on the local system
- GraphQL basics
If you have no prior experience using these technologies I would recommend using the listed resources to get the basics down before continuing with this post.
GraphQL Basics
GraphQL is a query language and runtime that can be used to build and expose APIs as a strongly typed schema instead of a messy REST endpoint. Users see that schema and can query for what fields they want in particular.
Here is a list of the key concepts you need to know about:
- Schema — Core of a GraphQL server implementation. Describes the functionality available to the client applications
- Query — Request to read or fetch values
- Mutation — Query that modifies data in the data store
- Type — Defines the structure of the data which is used in GraphQL
- Resolver — Collection of functions that generate response for a GraphQL query
Nestjs provides us with two different ways of building GraphQL applications, the schema first and the code first respectively.
- Schema first — In the schema first approach the source of truth is a GraphQL SDL (Schema Definition Language) and the Typescript definitions of your GraphQL schema will be auto generated by Nestjs
- Code first — In the code first approach you will only use declarators in your Typescript classes to generate the corresponding GraphQL schema
In this post I chose the code first approach because I personally find that it would make it easier to understand and follow this tutorial for people with little to none GraphQL experience.
If you want to know more about GraphQL and its concepts I would highly recommend looking at this resources:
- Official documentation
- How to GraphQL — Great beginners learning resources
- GraphQL crash course — Full crash course using GraphQL and React
Installing Nest.js and all dependencies
Now that we know what we are going to build and why we use the specific tools and technologies let’s get started by creating the project and installing the needed dependencies.
First let’s install the Nest CLI and use it to create the project
npm i -g @nestjs/cli
nest new nest-graphql
After that, lets move into the directory and install the needed dependencies
cd nest-graphql
npm i --save @nestjs/graphql apollo-server-express graphql-tools graphql @nestjs/mongoose mongoose type-graphql
Now that we have everything installed and the basic project setup out of the way let’s create the files we will work in.
Let’s start by creating the Module, Service and Controller using the Nest.js CLI
nest g module items
nest g service items
nest g resolver items
After that you just need to create some files and folders manually so your structure looks like this.
You can also find the structure on my Github.
Starting the server
With the setup process completed, you can now start the server with:
npm run start
This will start the application on the default port of 3000. Now you just need to navigate to http://localhost:3000 in your browser and should see something like this.
Adding GraphQL
Now that we have finished the basic server setup let continue by importing the GraphQL dependencies into our app module.
import { GraphQLModule } from '@nestjs/graphql';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
})]
export class AppModule {}
Here we import the GraphQLModule from @nest/graphql which we installed above. After that we use it in our imports statement using the forRoot() method which takes an option object as a parameter.
In the options file we just specify the name of the auto generated GraphQL file that gets created when we start the server. This file is part of the code first approach we took about above.
Connecting with MongoDB
Next up lets create a connection to the database of our application by importing the MongooseModule
into our ApplicationModule
.
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/nestgraphql')
],
})
export class AppModule {}
Here we use the forRoot()
function which accepts the same configuration object as the mongoose.connect()
function we are used to.
Database schema
The data schema is used to properly structure the kind of data that will be stored in the database of our application.
import * as mongoose from 'mongoose';
export const ItemSchema = new mongoose.Schema({
title: String,
price: Number,
description: String,
});
Here we define a schema by importing mongoose and using mongoose.Schema
to create a new object.
Interface
Next, we will create a Typescript interface which will be used for the type-checking in our service and receiver.
import { Document } from 'mongoose';
export interface Item extends Document {
readonly title: string;
readonly price: number;
readonly description: string;
}
DTO
The DTO (Data transfer object) is an object that defines how the data will be sent over the network.
import { ObjectType, Field, Int, ID } from '@nestjs/graphql';
@ObjectType()
export class ItemType {
@Field(() => ID)
readonly id?: string;
@Field()
readonly title: string;
@Field(() => Int)
readonly price: number;
@Field()
readonly description: string;
}
We also need to define an InputType
for all mutations that take a object as a parameter.
import { InputType, Field, Int } from '@nestjs/graphql';
@InputType()
export class ItemInput {
@Field()
readonly title: string;
@Field(() => Int)
readonly price: number;
@Field()
readonly description: string;
}
Importing the schema
Now that we have created all the needed files for our database we just need to import our schema into our ItemsModule
.
import { Module } from '@nestjs/common';
import { ItemsResolver } from './items.resolver';
import { ItemSchema } from './item.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { ItemsService } from './items.service';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Item', schema: ItemSchema }])],
providers: [ItemsResolver, ItemsService],
})
export class ItemsModule {}
Here we import the MongooseModule
like we did in the ApplicationModule
but use the forFeature()
function instead which defines what database model will be registered for the current scope.
Implementing GraphQL CRUD
Let’s continue by implementing the CRUD functionality using the Mongoose database and GraphQL endpoints.
Service
First let’s create the database CRUD functionality inside our Service.
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { ItemType } from './dto/create-item.dto';
import { Item } from './interfaces/item.interface';
import { ItemInput } from './input-items.input';
@Injectable()
export class ItemsService {
constructor(@InjectModel('Item') private itemModel: Model<Item>) {}
async create(createItemDto: ItemInput): Promise<ItemType> {
const createdItem = new this.itemModel(createItemDto);
return await createdItem.save();
}
async findAll(): Promise<ItemType[]> {
return await this.itemModel.find().exec();
}
async findOne(id: string): Promise<ItemType> {
return await this.itemModel.findOne({ _id: id });
}
async delete(id: string): Promise<ItemType> {
return await this.itemModel.findByIdAndRemove(id);
}
async update(id: string, item: Item): Promise<ItemType> {
return await this.itemModel.findByIdAndUpdate(id, item, { new: true });
}
}
Here we first import our database model in our constructor using dependency injection. After that we continue to implement basic CRUD functionality using the standard MongoDB functions.
Resolver
Now that we have implemented the CRUD functionality inside our service we just need to create our GraphQL resolver where we define the Query’s and Mutations needed for GraphQL.
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { ItemsService } from './items.service';
import { ItemType } from './dto/create-item.dto';
import { ItemInput } from './input-items.input';
import { Item } from './interfaces/item.interface'
@Resolver(of => ItemType)
export class ItemsResolver {
constructor(private readonly itemsService: ItemsService) {}
@Query(returns => [ItemType])
async items(): Promise<ItemType[]> {
return this.itemsService.findAll();
}
@Mutation(returns => ItemType)
async createItem(@Args('input') input: ItemInput): Promise<ItemType> {
return this.itemsService.create(input);
}
@Mutation(returns => ItemType)
async updateItem(
@Args('id') id: string,
@Args('input') input: ItemInput,
) {
return this.itemsService.update(id, input as Item);
}
@Mutation(returns => ItemType)
async deleteItem(@Args('id') id: string) {
return this.itemsService.delete(id);
}
@Query(returns => String)
async hello() {
return 'hello';
}
}
As you can see we create a class with different methods which make use of the ItemService we created earlier. But this class also comes with some pretty interesting declarators so let’s take a look at them:
@Resolver()
— Tells Nestjs that this class knows how to resolve the actions for our items.@Query()
— Query’s in GraphQL are basically the construct used by the client to request specific fields from the server. The declarator in this case just says that we can query that using the name of our function which we will do later.@Mutation()
— Mutations in GraphQL are very similar to Query’s but are more about mutating data then querying it.@Args()
— Is a helper declarator used to dig out the input parameters
E2E testing the Application
Now that we have completed our CRUD functionality let’s take a look at how we can test our application using the Jest testing library.
If you have never used Jest before I would recommend learning the basics first before continuing with this section.
First, we need to create a new test file using the following command.
touch test/items.e2e-spect.ts
After that, we can continue by creating the basic test setup for our module.
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { ItemsModule } from '../src/items/items.module';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { Item } from '../src/items/interfaces/item.interface';
describe('ItemsController (e2e)', () => {
let app;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ItemsModule,
MongooseModule.forRoot('mongodb://localhost/nestgraphqltesting'),
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
}
In this code block, we create a Nestjs instance with the three modules we need to test our AppController. We also define that the instance will be closed when all test have been completed.
Next up, we create two item objects that will be used in our HTTP requests.
const item: Item = {
title: 'Great item',
price: 10,
description: 'Description of this great item',
};
let id: string = '';
const updatedItem: Item = {
title: 'Great updated item',
price: 20,
description: 'Updated description of this great item',
};
After that, we can create a GraphQL query for testing the item creation functionality and use it in our HTTP request.
const createitemObject = JSON.stringify(item).replace(
/\"([^(\")"]+)\":/g,
'$1:',
);
const createItemQuery = `
mutation {
createItem(input: ${createitemObject}) {
title
price
description
id
}
}`;
it('createItem', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
query: createItemQuery,
})
.expect(({ body }) => {
const data = body.data.createItem;
id = data.id;
expect(data.title).toBe(item.title);
expect(data.description).toBe(item.description);
expect(data.price).toBe(item.price);
})
.expect(200);
});
Here we create a query and send it to our endpoint using the request function which lets us simulate HTTP request to our server.
Then we can use the expect function to validate the response from the request.
This process can be repeated for all endpoints and leaves us with the following result.
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { ItemsModule } from '../src/items/items.module';
import { MongooseModule } from '@nestjs/mongoose';
import { GraphQLModule } from '@nestjs/graphql';
import { Item } from '../src/items/interfaces/item.interface';
describe('ItemsController (e2e)', () => {
let app;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ItemsModule,
MongooseModule.forRoot('mongodb://localhost/nestgraphqltesting'),
GraphQLModule.forRoot({
autoSchemaFile: 'schema.gql',
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
const item: Item = {
title: 'Great item',
price: 10,
description: 'Description of this great item',
};
let id: string = '';
const updatedItem: Item = {
title: 'Great updated item',
price: 20,
description: 'Updated description of this great item',
};
const createitemObject = JSON.stringify(item).replace(
/\"([^(\")"]+)\":/g,
'$1:',
);
const createItemQuery = `
mutation {
createItem(input: ${createitemObject}) {
title
price
description
id
}
}`;
it('createItem', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
query: createItemQuery,
})
.expect(({ body }) => {
const data = body.data.createItem;
id = data.id;
expect(data.title).toBe(item.title);
expect(data.description).toBe(item.description);
expect(data.price).toBe(item.price);
})
.expect(200);
});
it('getItems', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
query: '{items{title, price, description, id}}',
})
.expect(({ body }) => {
const data = body.data.items;
const itemResult = data[0];
expect(data.length).toBeGreaterThan(0);
expect(itemResult.title).toBe(item.title);
expect(itemResult.description).toBe(item.description);
expect(itemResult.price).toBe(item.price);
})
.expect(200);
});
const updateItemObject = JSON.stringify(updatedItem).replace(
/\"([^(\")"]+)\":/g,
'$1:',
);
it('updateItem', () => {
const updateItemQuery = `
mutation {
updateItem(id: "${id}", input: ${updateItemObject}) {
title
price
description
id
}
}`;
return request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
query: updateItemQuery,
})
.expect(({ body }) => {
const data = body.data.updateItem;
expect(data.title).toBe(updatedItem.title);
expect(data.description).toBe(updatedItem.description);
expect(data.price).toBe(updatedItem.price);
})
.expect(200);
});
it('deleteItem', () => {
const deleteItemQuery = `
mutation {
deleteItem(id: "${id}") {
title
price
description
id
}
}`;
return request(app.getHttpServer())
.post('/graphql')
.send({
operationName: null,
query: deleteItemQuery,
})
.expect(({ body }) => {
const data = body.data.deleteItem;
expect(data.title).toBe(updatedItem.title);
expect(data.description).toBe(updatedItem.description);
expect(data.price).toBe(updatedItem.price);
})
.expect(200);
});
});
That’s it now we just have to run the tests using the following command.
npm run test:e2e
Testing the application
Now that we have finished building our simple CRUD application let’s test it using the GraphQL playground. For that let’s start the server and then navigate to our playground.
npm run start
After starting the server you should be able to see your GraphQL playground on http://localhost:3000/graphql.
Next let’s continue by writing a mutation for creating an item.
mutation {
createItem(input: {title: "item", price: 10, description: "test"}) {
title
price
description
id
}
}
After that let’s test our get functionality using this simple query:
{
items {
title,
price,
description,
id
}
}
Now only the update and delete functionality remains. For that we are going to need the id of the item we created earlier which you can get by using our items query.
Now lets use this id in our next two actions.
mutation {
updateItem(id: "608aa7f2a6af145da8346042", input: {title: "item123", price: 10, description: "test123"}) {
title
price
description
id
}
}
You can also delete an item.
mutation {
deleteItem(id: "608aa7f2a6af145da8346042") {
title
price
description
id
}
}
The full code of this tutorial can also be found on my Github.
Conclusion
You made it all the way until the end! Hope that this article helped you understand the basics GraphQL and how to use it in Nestjs.
If you have found this useful, please consider recommending and sharing it with other fellow developers. If you have any questions or feedback, let me know in the comments down below.