Skip to content

Building a GraphQL Server in Nestjs

Updated: at 15:00

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:

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:

Nestjs provides us with two different ways of building GraphQL applications, the schema first and the code first respectively.

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:

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.

Folder Structure

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.

Nestjs Hello World

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:

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.

GraphQL Item response

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.