loading...

Building a Production-grade Nodejs,GraphQL and TypeScript Server - Testing and Docker

ganeshmani profile image GaneshMani Originally published at cloudnweb.dev ・5 min read

So far we have seen, How to setup and build a GraphQL and Typescript application. In this article, we will see how to test our graphql endpoints and dockerize our application. Building a Production-grade Nodejs,GraphQL and TypeScript Server - Testing and Docker.

Part 1

Part 2

Previous article, covers how to write login query and register mutation with type-graphql. we will see how to set the graphql endpoints using jest.

Firstly, let's setup jest in our application.

npm i --save-dev jest

Well, jest is enough if it is javascript. To test a typescript application, we might need to install few more dependancies here.

npm i --save-dev @types/jest ts-jest

On the above code, we install jest types and ts-jest to run testing for typescript code. After that, create a config file for jest.

jest.config.js

module.exports = {
  roots: ["__test__"],
  transform: {
    "^.+\\.tsx?$": "ts-jest",
  },
  testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
}

Firstly, we set the root directory for test files. it depends on where we create our test file directory.

After that, we try to run all the files that matches our testRegex using ts-jest that we installed as a dependancy.

Finally, we have the moduleFileExtensions which supports the mentioned file extensions in the array.

MongoDB setup for testing

We need to connect with database to test our graphql endpoints. to do that, we need a database setup. let's create one for testing.

create a file db.ts inside our test directory and add the following code,

import * as mongoose from "mongoose"
import UserModel from "../src/UserService/UserModel"
require("dotenv").config()

// mongoose.Promise = global.Promise;

const models = {
  user: UserModel,
}

export const cleanDB = async (cb: any) => {
  await models.user.deleteMany({})
  cb()
}

export const connectToDB = async () => {
  const MONGODB_USER = process.env.MONGODB_USER || "root"
  const MONGODB_PASSWORD = process.env.MONGODB_PASS || "Root!23456"
  const connection = await mongoose.connect(
    `mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@ds237620.mlab.com:37620/project-board-app`
  )

  return connection
}

export const disconnectDB = (cb = () => {}) => {
  mongoose.disconnect(cb)
}

export const generateMongooseId = () => {
  return mongoose.Types.ObjectId()
}

On the above code, we have four methods. they are,

  • Clean the MongoDB
  • Connect with the MongoDB
  • Disconnect with MongoDB
  • generate Mongoose ID

Now, we have method to connect/disconnect with database. we need a way to test our graphql endpoint. to make it happen, we need to build graphql schema for testing. let build our graphql schema,

GraphQL setup for testing

create a file run.ts inside test directory and add the following code,

import { UserResolver } from "../src/UserService/UserResolver"
import { graphql, Source } from "graphql"
import { buildSchema } from "type-graphql"
import { Container } from "typedi"
import UserModel from "../src/UserService/UserModel"

export const runQuery = async (query: string, variables: any, ctx = {}) => {
  Container.set({ id: "USER", factory: () => UserModel })

  const schema = await buildSchema({
    resolvers: [UserResolver],
    emitSchemaFile: true,
    nullableByDefault: true,
    container: Container,
  })
  return graphql(schema, query, null, {}, variables)
}

On the above code, we build a schema just like we do in our server code and return graphql instance which is used to run graphql endpoints.

Containers are used to inject the dependancy for our graphql endpoint testing. checkout the second part to know more about dependancy injection.

finally,we came to the API testing part. i am telling you, this is an interesting part. because, it helps you to improve your code quality in some way. i personally felt that testing helps a lot in development.

Query & Mutation testing

create a file api/user.resolver.spec.ts and import the required dependancies.

const db = require("../db")
import { runQuery } from "../run"
import { GraphQLError } from "graphql"

If you're completely new to the world of testing, i recommend you to checkout this video.

Firstly, we need to connect with our database before running any test cases. connect with database using jest beforeAll method.

beforeAll(db.connectToDB)

At the same time, we need to disconnect and clean the data from database after we run the test cases successfully.

afterAll(db.disconnectDB)

afterAll(db.cleanDB)

Each test cases should be defined as it in jest or most of the testing frameworks.

describe("Register Mutation", () => {
  beforeAll(db.connectToDB)

  afterAll(db.disconnectDB)

  afterAll(db.cleanDB)

  const registerMutation = `
mutation Register($name: String!,$email : String!,$password : String!) {
  registerUser(
  name : $name,email : $email,password: $password
  ) {
    success
    data{
      name 
      email
    }
    error
  }
}
`
  it("run successfully", async () => {
    const user = {
      name: "test",
      email: "test@gmail.com",
      password: "123456",
    }

    const response = await runQuery(registerMutation, {
      name: user.name,
      email: user.email,
      password: user.password,
    })
    expect(response).toMatchObject({
      data: {
        registerUser: {
          success: true,
          data: {
            name: user.name,
            email: user.email,
          },
          error: null,
        },
      },
    })
  })
})

On the above code, we have register mutation and runQuery which run the graphql mutation and returns the result.

const registerMutation = `
mutation Register($name: String!,$email : String!,$password : String!) {
  registerUser(
  name : $name,email : $email,password: $password
  ) {
    success
    data{
      name 
      email
    }
    error
  }
}
`

you can mock the data or we can use some libraries such as fakerjs to mock the data.

const user = {
  name: "test",
  email: "test@gmail.com",
  password: "123456",
}

const response = await runQuery(registerMutation, {
  name: user.name,
  email: user.email,
  password: user.password,
})

you can use any matchers from jest to test the result. Here, we match the result object to be the expected.

expect(response).toMatchObject({
  data: {
    registerUser: {
      success: true,
      data: {
        name: user.name,
        email: user.email,
      },
      error: null,
    },
  },
})

In similar way, write a test case for login query. let's run the test cases and see the result.

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ%20f293f39e08204372a13477dab83a0500/testing-graphql.png

Now, we have test cases for our graphql server. let's dockerize our application and run our application as docker container.

Dockerizing GraphQL,TypeScript server

I assume that you have some knowledge on what is docker and how docker works. if you're new to docker and nodejs dockerization. checkout this article on dockerizing nodejs server.

Here, we are going to follow a multi-stage build on typescript,graphql server.

create a Dockerfile in your root directory and add the following code,

#stage1
FROM node as builder
WORKDIR /usr/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

#stage 2
FROM node
WORKDIR /usr/app
COPY package*.json ./
RUN npm install --production

COPY --from=builder /usr/app/dist ./dist

COPY .env .
CMD node dist/index.js

Let's understand what's happening on the above code.

Stage1

  1. We install the nodejs with alias name builder
  2. Set /usr/app as the Working directory
  3. Copying the package.json to the docker root directory.
  4. we don't want to copy the node_modules to docker directly. it is cumbersome. instead we copy the package file and run npm install
  5. Finally, we run npm run build to take the compiled javascript from typescript

Stage2

  1. On final docker image, we install node
  2. Set /usr/app as working directory
  3. Copying the package.json to the docker root directory.
  4. Run npm install which install only the production dependancies
  5. Copy all the compiled js from intermediate docker container to final container.
  6. Finally, run the application using node index.js

Complete source code can be found here

Found any issues or Comments. Create it as issue in github here.

Posted on May 29 by:

ganeshmani profile

GaneshMani

@ganeshmani

Full Stack Developer. Working on Node, Angular, PHP

Discussion

markdown guide