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

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.

1npm 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.

1npm 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

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

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

graphql ts testing

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,

1import * as mongoose from "mongoose"
2import UserModel from "../src/UserService/UserModel"
3require("dotenv").config()
4
5// mongoose.Promise = global.Promise;
6
7const models = {
8 user: UserModel,
9}
10
11export const cleanDB = async (cb: any) => {
12 await models.user.deleteMany({})
13 cb()
14}
15
16export const connectToDB = async () => {
17 const MONGODB_USER = process.env.MONGODB_USER || "root"
18 const MONGODB_PASSWORD = process.env.MONGODB_PASS || "Root!23456"
19 const connection = await mongoose.connect(
20 `mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@ds237620.mlab.com:37620/project-board-app`
21 )
22
23 return connection
24}
25
26export const disconnectDB = (cb = () => {}) => {
27 mongoose.disconnect(cb)
28}
29
30export const generateMongooseId = () => {
31 return mongoose.Types.ObjectId()
32}

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,

1import { UserResolver } from "../src/UserService/UserResolver"
2import { graphql, Source } from "graphql"
3import { buildSchema } from "type-graphql"
4import { Container } from "typedi"
5import UserModel from "../src/UserService/UserModel"
6
7export const runQuery = async (query: string, variables: any, ctx = {}) => {
8 Container.set({ id: "USER", factory: () => UserModel })
9
10 const schema = await buildSchema({
11 resolvers: [UserResolver],
12 emitSchemaFile: true,
13 nullableByDefault: true,
14 container: Container,
15 })
16 return graphql(schema, query, null, {}, variables)
17}

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.

1const db = require("../db")
2import { runQuery } from "../run"
3import { 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.

1beforeAll(db.connectToDB)

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

1afterAll(db.disconnectDB)
2
3afterAll(db.cleanDB)

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

1describe("Register Mutation", () => {
2 beforeAll(db.connectToDB)
3
4 afterAll(db.disconnectDB)
5
6 afterAll(db.cleanDB)
7
8 const registerMutation = `
9mutation Register($name: String!,$email : String!,$password : String!) {
10 registerUser(
11 name : $name,email : $email,password: $password
12 ) {
13 success
14 data{
15 name
16 email
17 }
18 error
19 }
20}
21`
22 it("run successfully", async () => {
23 const user = {
24 name: "test",
25 email: "test@gmail.com",
26 password: "123456",
27 }
28
29 const response = await runQuery(registerMutation, {
30 name: user.name,
31 email: user.email,
32 password: user.password,
33 })
34 expect(response).toMatchObject({
35 data: {
36 registerUser: {
37 success: true,
38 data: {
39 name: user.name,
40 email: user.email,
41 },
42 error: null,
43 },
44 },
45 })
46 })
47})

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

1const registerMutation = `
2mutation Register($name: String!,$email : String!,$password : String!) {
3 registerUser(
4 name : $name,email : $email,password: $password
5 ) {
6 success
7 data{
8 name
9 email
10 }
11 error
12 }
13}
14`

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

1const user = {
2 name: "test",
3 email: "test@gmail.com",
4 password: "123456",
5}
6
7const response = await runQuery(registerMutation, {
8 name: user.name,
9 email: user.email,
10 password: user.password,
11})

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

1expect(response).toMatchObject({
2 data: {
3 registerUser: {
4 success: true,
5 data: {
6 name: user.name,
7 email: user.email,
8 },
9 error: null,
10 },
11 },
12})

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.

graphql multistage

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

1#stage1
2FROM node as builder
3WORKDIR /usr/app
4COPY package*.json ./
5RUN npm install
6COPY . .
7RUN npm run build
8
9#stage 2
10FROM node
11WORKDIR /usr/app
12COPY package*.json ./
13RUN npm install --production
14
15COPY --from=builder /usr/app/dist ./dist
16
17COPY .env .
18CMD 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.

To Read More

Kubernetes for Nodejs developers

Do you keep hearing the word kubernetes in the tech community and you couldn't u...

TypeScript Interfaces vs Types

In this article, we will see what are interfaces and types and the difference be...

How to find project ideas to practi...

Ever wondered what how to get a real world experience on web development while w...

Building a Production-grade Nodejs,...

This article is the first part of building a production grade nodejs,graphql and...

Modern React Redux Tutorials with R...

This tutorial explain how you can build an application using modern react redux ...

Building a Piano with React Hooks

In this article, we will see how to build a piano with react hooks. Building a P...

TypeScript Basics - The Definitive ...

In this article, we will learn some basics of typescript which helps you to deve...

Here's why podman is more secured t...

In this article we will see about podman and why it is more secured way to run c...

What is gRPC ? How to implement gRP...

Everyone talks about gRPC. Have you ever wonder how it works or how to implement...