Building a GraphQL Server in Nestjs

Building a GraphQL Server in Nestjs

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 a 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.

Prerequisites

To follow this blogpost I would recommend to have at minimum a basic knowledge about these technologies and install the listet 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 mesy REST endpoint. Users see that schema and can query for what fields they want in particullar.

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 personaly find that it would make it easier to understand and follow this tutorial for people with little to none GraphQL experiece.

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.

folderstructure

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
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 an parameter.

In the options file we just specify the name of the auto generated GraphQL file that get’s created when we start the server. This file is part of the code first approach we taked about above.

Connectiong 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

Lets continue by implementing the CRUD functionality using the Mongoose database and GraphQL endpoints.

Service:

First let’s create the database CRUD functionality inside of 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 of our service we just need to create our GraphQL resolver where we define the Querys 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() — Querys in GraphQL are basicly 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 Querys 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 than 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.

Response of the get request
Response of the get request

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.