Part 1:- API using GraphQL and Node.js
This article is for people who are interested in learning to write optimized code using GraphQL with unit testing using mocha and chai.
Before we start, we need to define the functionality of our API. The application will be a simple todo app. It will create a user in a database who will be able to create, get and delete todos. If you don’t understand the code or get stuck somewhere, you can check out the code from my github repository linked at the end of the series of this article.
About the series
This is a series of 3 articles which will help to write production grade code. I wanted to help others who might have faced same problems as me. This article can help a beginner and also to someone who has good knowledge in node.
What you will learn?
- TypeScript (if you don’t write it already).
- Using Docker for Node projects.
- Writing a GraphQL API instead of REST.
- Basics of MongoDB.
- Logging.
- Working with JWT(Json Web token).
- Writing tests and working with code coverage.
- Writing clean code and linting.
- Writing modular code.
- Basics of Promises, async and await.
- Writing production grade code.
In this Article
In this article you will be able to do the following things:-
- Setup project
- Setup docker
- Write express server and connect to mongodb
- Write tests for above setup.
Requirements
- Node and npm installed for your OS. We will use node environment to work on this project.
- Docker installed for your OS. Along with local working of project, you will learn little bit of docker and how to work on projects if you don’t have node and npm installed.
- GraphQL-Playground for testing your API. This is an awesome tool where you will test your APIs and learn how to use this tool.
- Important :- I will teach how to work with project locally and also how to work with Docker side by side. Those who want to work only with docker, I will refer to them as
non npm
users. Also we will try to follow TDD(test driven Development) approach.
Project Setup
Open your development folder and create a new Folder for our project and go to it or copy the following command.
mkdir graphql-todo
Now open your code editor inside that folder.
If you have npm
installed for your OS run
npm init -y
. It will create a package.json
file like this
{
"name": "graphql-todo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
non npm
users just create a package.json
file and copy the above contents. You can edit description to anything you like.
We will be writing code in Typescript so we will need something that will compile our code fast and minify the process. So for this we will use Gulp. Run the given command below in your terminal inside the project.
npm i --quiet gulp gulp-sourcemaps gulp-typescript typescript --save-dev
non npm
users copy this to your package.json
file.
"devDependencies": {
"gulp": "^4.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-typescript": "^5.0.0",
"typescript": "^3.2.2"
}
Now you have gulp and typescript installed. We will now create a gulpfile and write our code to minify the process of building the project. Follow the step below.
Create a gulpfile.js
inside root directory and copy this code.
Explanation
- Line 1–3:- We are requiring our modules.
- Line 4: We are using API of
gulp-typescript
and creating our TS project usingtsconfig.json
file which we will create later. - Line 8:- We are using Gulp API
task
and creating a task namebuild
. - Line 9–10:- We are using Gulp API
src
and takingprocess.yml
file as source(we will create it later) and piping it to our destination folder dist. Heregulp.src
creates a readble stream and with the help of node streams we pipe it to a writable stream created bygulp.dest
. - Line 12–17:- We are copying our whole code as per
tsconfig.json
and converting it to Javascript and pasting it to dist folder. - Line 20–22:- We are creating another task
watchTask
for watching any changes in ourbuild
task. - Line 24–25:- We are finally using Gulp’s
default
API for runnig build task andwatch
API for watching changes tobuild
andwatch
task.
Create a tsconfig.json
file in root directory and copy this code.
Important:- Do notice baseUrl
and outDir
. baseUrl is where we will write our TS code and outDir is where gulp will compile and copy our code to JavaScript.
Now create a process.yml
file in root directory and copy this code.
We will learn why we are creating this file later on. After this create an empty directory src
in our root folder. Up until now you might have project config like this. Those who are going to work only with docker,
they will not have package-lock.json
and node_modules.
To keep track of our work we will be using git. If not installed you can go through git to install it.
In your project initialize your repository as git repository by git init
.
For working in developement version we will need nodemon
and ts-node
for reloading our server automatically when any change is made in our file and executing our TS file without building them respectively. We will use TSlint as our code linter. In your terminal inside project directory run this command.
npm i --quiet nodemon ts-node tslint tslint-eslint-rules --save-dev
non npm
users just copy this to package.json file under devDependencies
.
"nodemon": "^1.18.9",
"ts-node": "^7.0.1",
"tslint": "^5.12.0",
"tslint-eslint-rules": "^5.4.0",
Create a tslint.json
file inside root directory and copy the code given below.
Inside scripts
in package.json
copy the following code.
"dev": "nodemon --no-deprecation --watch 'src/**/*.ts' --ignore 'src/**/*.spec.ts' --exec 'ts-node' src/app.ts",
"lint": "tslint -c tslint.json 'src/**/*.ts' --fix",
We will use npm run dev
for running our code in development mode and npm run lint
to lint our code. Now our actual coding will begin. Create a file inside src
folder named as app.ts
Copy this inside src/app.ts
function testFunction(): string {
return "Messi and Ronaldo are legends";
}export default testFunction;
Now we will test this. Create a test
folder inside src
, and then create a file user-test-spec.ts
. For testing we will install required modules. We will be using mocha
for testing and chai
for assertion. Run this command inside your project.
npm i --quiet chai chai-http chai-as-promised mocha mocha-typescript sinon --save-dev
non npm
users copy this inside devDependencies
.
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-http": "3.0.0",
"mocha": "^5.2.0",
"mocha-typescript": "^1.1.17",
"sinon": "^7.2.2",
We will create a script for testing. Copy this code inside scripts
"test": "mocha --no-deprecation --timeout 10000 --require ts-node/register **/*.spec.ts"
We can use this command using npm test
. So far our package.json will look like this.
{
"name": "graphql-todo",
"version": "1.0.0",
"description": "This is a GraphQL API for todo application",
"main": "index.js",
"scripts": {
"dev": "nodemon --no-deprecation --watch 'src/**/*.ts' --ignore 'src/**/*.spec.ts' --exec 'ts-node' src/app.ts",
"lint": "tslint -c tslint.json 'src/**/*.ts' --fix",
"start": "gulp && cd dist/ && node --inspect=8990 --no-deprecation app.js",
"test": "mocha --no-deprecation --timeout 10000 --require ts- node/register **/*.spec.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-http": "3.0.0",
"gulp": "^4.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-typescript": "^5.0.0",
"mocha": "^5.2.0",
"mocha-typescript": "^1.1.17",
"nodemon": "^1.18.9",
"sinon": "^7.2.2",
"ts-node": "^7.0.1",
"tslint": "^5.12.0",
"tslint-eslint-rules": "^5.4.0",
"typescript": "^3.2.2"
}
}
Inside your src/test/user-test.spec.ts
copy this code
Explanation:-
- Line 1–4:- We are importing test modules and our file
app.ts
- Line 7–8:- We are defining suite to run and defining our class UserTests.
- Line 10–16:-
@test
will declare the test message we are running, we also define a public test to run. We usechai.expect
to test assetion of test.
Run your test by running npm test
. If everything works fine, it will look like this.
To calculate test coverage of our code we will use nyc. We need to first check that our project is building or not. Copy this to scripts
and then run npm run build
"build": "gulp"
You will get output as show below and a dist
folder will be created.
Add nyc
module by running npm i --quiet nyc --save-dev
, non npm
users copy this into package.json
"nyc": "^13.1.0"
Add the command given below to scripts
.
"coverage": "nyc --reporter=text mocha --no-deprecation --timeout 10000 dist/test/*.spec.js -x dist/test/*.spec.js",
We need to build to latest code first, then after that we will run code coverage by running npm run build && npm run coverage
. This will create a .nyc_output
folder, from which our code coverage will be seen. You will see following output.
Above image shows that we have achieved 💯 code coverage which depicts that we have not written any unusable code.
Docker Setup
Now we will setup docker so that non npm
users can also run tests and build project without installing node or npm.
I assume that the readers have a basic understanding of docker. If you have never used docker before you might wanna check out this guide for getting started with Docker.
Create a Dockerfile inside root directory and copy this
Explanation:-
- Line 1:- We are use Node image having version 8.
- Line 4:- We globally install gulp and pm2. PM2 is a Production Runtime and Process Manager for Node.js applications with a built-in Load Balancer.
- Line 7:- We create a new directory
/usr/src/
inside our docker image. - Line 9:- We set
/usr/src/
as our working directory inside the docker image. - Line 12:- We copy
package.json
from root directory to our working directory. - Line 14:- We install node modules inside docker image.
- Line 17:- We copy rest of our code from root directory to our working directory.
- Line 20:- We build our project using gulp. We already installed gulp inside our docker image in line 4.
- Line 22:- We set our working directory as
/usr/src/dist/
as our JS code is compiled there. - Line 26:- We execute
pm2
and using ourprocess.yml
file that we created earlier to start the application inside docker image.
We need to build docker image using this Dockerfile. Add a scripts
folder inside root directory and create a dockerCompose.sh
file inside it. Copy the following code inside scripts/dockerCompose.sh
:
#!/usr/bin/env bashcd ..docker build -t knrt10/todoapi -f Dockerfile .docker-compose up -d
Add this inside scripts
in your package.json
.
"dockerStart": "cd scripts && chmod 777 dockerCompose.sh && ./dockerCompose.sh && cd ..",
"dockerStop": "docker-compose down"
Now that you know how to create an image with a Dockerfile
, let’s create an application as a service and connect it to a database. Then we can run some setup commands and be on our way to creating rest of application.
The Docker Compose file will define and run the containers based on a configuration file. We are using compose file version 2 syntax, and you can read about it on Docker’s site.
An important concept to understand is that Docker Compose spans buildtime and runtime. Up until now, we have been building images using docker build .
, which is buildtime. This is when our containers are actually built. We can think of runtime as what happens once our containers are built and being used.
Compose triggers buildtime — instructing our images and containers to build — but it also populates data used at runtime, such as env vars and volumes. This is important to be clear on. For instance, when we add things like volumes
and command
, they will override the same things that may have been set up via the Dockerfile at buildtime.
Open your docker-compose.yml
file in your editor in root directory and copy/paste the following lines:
Explanation:-
The first directive in the web service is to build
the image based on our Dockerfile
. This will recreate the image we used before, but it will now be named according to the project we are in, graphql-todo. After that, we are giving the service some specific instructions on how it should operate:
- Line 5:- Once the image is built, and the container is running, the
npm start
command will start the application. - Line 6:-
volumes:
– This section will mount paths between the host and the container. - Line 7:-
.:/usr/src/
– This will mount the root directory to our working directory in the container. - Line 8:-
/usr/src/node_modules
– This will mount thenode_modules
directory to the host machine using the buildtime directory. - Line 9:-
ports:
– This will publish the container’s port, in this case3000
, to the host as port3000
- Line 11:- This depicts on what database image it depends on. We are using mongodb so we will specify
mongodb
- Line 14:- It will build mongodb image.
- Line 15:- Same as nodejs we specify version of mongodb image here.
- Line 17:-
ports:
– This will publish the container’s port, in this case27017
, to the host as port27017
Now before executing this script, check your docker is up and running. This command will execute our scripts/dockerCompose.sh
file. This file builds our docker image. Now run npm run dockerStart
If everything goes right you will see this output. This means your docker image is created.
Successfully built 2597b7c50ed4
Successfully tagged graphql-todo_web:latest
Creating graphql-todo_mongodb_1 ... done
Creating graphql-todo_web_1 ... done
You can get information about your running containers using the command given below.
docker ps -a
You will see this kind of output
Copy the NAMES for image graphql-todo_web
and run this
docker exec -it graphql-todo_web_1 bash
This will run bash inside the existing graphql-todo_web_1
container. The bash process will have the same Linux namespaces as the main container process. This allows you to explore the container from within and see how Node.js and your app see the system when running inside the container. The -it option is shorthand for two options:
- -i, which makes sure STDIN is kept open. You need this for entering commands into the shell.
- -t, which allocates a pseudo terminal (TTY).
Exploring container from within
Now you can run same commands as other users who have node installed. Find your test coverage by running the command given below.
npm run coverage
You will see the following output.
You can stop docker process by running npm run dockerStop
after exiting from container.
Setting up Express server
We will now setup our express server. Copy this code to your package.json
and run npm i --quiet
. Non npm
users just copy.
"dependencies": {
"@types/node": "^10.12.15",
"bcrypt-nodejs": "0.0.3",
"bluebird": "^3.5.3",
"cors": "^2.8.5",
"express": "^4.16.4",
"helmet": "^3.15.0",
"jsonwebtoken": "^8.4.0",
"mongoose": "^5.4.0",
"winston": "2.4.0"
}
Explanation:-
- bcrypt-nodejs:- Its for hashing user password using gensalt and securely storing into database
- bluebird:- For handling promises.
- cors:- For handling Cross origin resource sharing and enabling it across all headers and domains.
- express:- For setting up server.
- helmet:- For securing our Express apps by setting various HTTP headers.
- jsonwebtoken:- For creating token that will authenticate the user.
- mongoose:- A ORM(Object-Relational Mapping) to interact with mongoDB.
- winston:- A cool logger instead of
console.log
.
Now create a server.ts
inside src
folder and copy this
Explanation:-
- Line 1–10:- We import necessary modules.
- Line 11:- We specify that our promises globally will be handled by bluebird.
- Line 24-30:- We create a class
TodoApp
and initialize it’s constructor with a public data member infostring and set it to according to what type of mongoDB we are using. If running locallyauthEnabled
is set to false inconfig.ts
file. If running dockerlocalDatabase
insideconfig.ts
is set to false. - Line 46 :- We create a public member function to start the express server.
- Line 70–77:- Here we check whether we have a
logs
folder or not. We are returning a promise here that we handle in line 47. - Line 83–127:- We are using
winston
as our logger. So here we setup winston that’s all. - Line 133–144:- We create a member function to initialize our express application.
- Line 150–152:- We are intializing cors module in this member function.
- Line 158–160:- In this member function we will initialize our graphQL routes in our next article.
- Line 167–175:- In this member function we are returing a promise that resolves to that a mongoDB is connected. We handle this promise on line 52.
Now create a shared
folder inside src/
and create 2 files
config.ts
index.ts
Copy this to src/shared/config.ts
And this inside src/shared/index.ts
export * from "./config";
Now copy this to your src/app.ts
"use strict";import { TodoApp } from "./server";const server: TodoApp = new TodoApp(process.env.API_PORT || 3000);// starting the serverserver.startServer();
Also create a logs
folder inside project root directory and create a temp.txt
file inside it. Copy the following code insidelogs/temp.txt
:
This is just an example of log file{"level":"info","message":"Winston has been init","timestamp":"2018-12-26T16:59:05.380Z"}
{"level":"info","message":"Mongo Connected!","timestamp":"2018-12-26T16:59:05.420Z"}
{"level":"info","message":"Express started on (http://localhost:3000/)","timestamp":"2018-12-26T16:59:05.430Z"}
{"version":"1.0.0","level":"info","message":"graphql-todo startup sequence completed","timestamp":"2018-12-26T16:59:05.432Z"}
To update our tests. Copy this to src/test/user-test.spec.ts
:
First start your mongoDB for your OS and then run npm run build && npm run coverage
. You will see the output given below.
Commiting our changes
We need to commit our changes, so that if some problem occur we can roll back to this commit. First create a .gitignore
file in your root directory and copy this
# Dependency directoriesnode_modules
jspm_packages# Optional npm cache directory
.npm# Optional REPL history
.node_repl_history
/bin/
/tmp/
/dist/
/typings/
/data/
/logs/*.log
/.nyc_output/
/coverage/
.DS_Store
package-lock.json
Now copy and run the commands given below inside your git repository. Make sure you are in root folder.
git add .
git commit -m "Adding part1 changes"
So far your project should look like this
Conclusion
That is for this part. In this part you learnt following things:-
- How to create express server.
- How to setup docker using dockerfile and docker-compose.yml.
- How to use Gulp and compile code from TS to JS.
- How to write clean code.
- How and write tests and have 💯 code coverage 😃.
- In next part you will start working with graphQL and make API using that.
- You can find Part 2:- here
- You can find Part 3:- here
Support
I wrote this series of articles by using my free time. A little motivation and support helps me a lot. If you like this nifty hack you can support me by doing any (or all 😉 ) of the following: