If you want to use the server starter directly without going through the tutorial, find the code on Github. Link to the next parts are at the bottom of this page.
I recently needed to build a server for a side project. Building the foundation to provide generic features any server is expected to deliver ended up being a new side project by itself!
Today there are many great tools to build a robust and versatile back-end, and plenty documentation available. However it is hard to find tutorials for building a generic back-end, with very little configuration needed to adapt it to any business application. It is also hard to find info about grouping all the different tools and frameworks needed together. This tutorial is nothing more than a summary of all the tribal knowledge spread out there.
Let’s cover this topic, by building only the foundation of our server, which will then be easily extensible and using a modern tech stack:
- With Koa.js we will implement our main logic. It is the core of our server and it will tie all the components together.
- MongoDB will allow us to manage a database to store and retrieve information.
- GraphQL will allow us to implement an API to interact with this database. Coupling GraphQL with Typescript will allow us to use GraphQL Code Generator to have a single source of truth for our GraphQL, Typescript and MongoDB models. 🤯
- Using Mocha, we will ensure our server behaves correctly by testing both our GraphQL endpoints and our MongoDB CRUD methods (database accessors).
- Docker will facilitate the deployments and the installation of all the tools for each developer of the team, without asking them to install a bunch of software on their machine.
- We will also cover some Quality Of Life (QOL) tooling: this means using tools like eslint, git hooks and nodemon. It will speed up the iterations when writing code.
Using all these tools together will make our server starter generic enough to be used for all kinds of projects, with an efficient development and deployment workflow. There are many steps, so this tutorial will be broken down into multiple chapters. Try not to be discouraged by the big picture and to see each one of the chapters as a small bit that is very easy to set up individually. Let’s get started!
Getting started: a Typescript-ready project
First things first, create a git repository, and run
npm init. You can then safely remove the “main” and “scripts.test” entries from the package.json file, as we will rewrite them later.
Make the project Typescript ready by running
npm i -D typescript @types/node
“i” stands for install and “-D” for save as a devDependency. Indeed, we won’t need the typescript package at runtime, only at compile time and when developing. Registering as many packages as possible as devDependencies will later save some space in the Docker container of our app.
How does Typescript work with Node?
This means we need a tsconfig.json file to configure the compilation. Copy/paste the following into
Basically we’re asking
tsc to include all files inside
src and to put the generated files into the
Let’s add our first script to our
package.json. We will now be able to build our typescript project by running
npm run build:
But in order to compile something we need sources! Let’s create a basic Koa.js server. Run
npm i koa koa-router @types/koa-router
and create an
src folder, with a
server.ts file inside.
It’s quite straightforward. By hovering on any object in your IDE you can see that everything is strongly typed! Do not mind the
process.env.SERVER_PORT now, it will be useful later. Just remember that until we define this variable somewhere, the OR part (
||) will be used, in that case, 3100.
Let’s try running
npm run build, and then
node ./dist/server.js to start the server. The
console.log() call at line 10 should output the server’s URL. Browse
http://localhost:3100/health to use our only route and verify that we have an “Ok” with a 200 HTTP response.
Some Quality of Life (QoL) tooling
From now on we’d like to ensure our code always meets some quality standards. Nicely formatted code = better readability, no irrelevant git changes like spaces and extra lines, and happier developers. Also, running
npm run build and starting the server manually each time is not a great workflow. Let’s remediate to that.
📃 Enforce the code convention
Let’s add eslint to our project. It will make sure the code convention is enforced. Run
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
and create a
A couple of things:
- We are using the code convention of the extensions listed under
extends. This is recommended for all typescript projects, and there are many other extensions available.
- We can add as many custom rules as we want. For instance, I like making sure all the console.log() calls are removed in the final code, to avoid the typical
console.log("debug")calls (or worse 😉).
To enforce these rules instantly when you are writing some code, you will need to install the ESLint plugin in your IDE.
You will also need a
.eslintignore file, in which you’ll list the folders to exclude from the linting. Add
dist, as we don’t want to lint the files the typescript compiler generated.
Now you may notice an eslint error in src/server.ts (with VSCode you have to restart the editor to see ESLint errors after installing it):
As we’d like to keep this valuable output to know if the server actually started or not, we can make it an exception. Add the following line just before the console.log() call:
// eslint-disable-next-line no-console
We’re now ready to add another script to the package.json’s scripts section:
"lint": "eslint . --fix"
npm run lint to automatically fix all fixable code convention errors and report the others.
Avoid pushing ill-formatted code to the remote
Wouldn’t it be nice if the linting job was run each time we committed some files, in order to make sure we only save clean code? Let’s add some git hooks. They are small scripts that trigger on specific git actions (commit, push…).
Adding husky to our project is the easiest way to make everyone in the team use git hooks:
npm i -D husky. Add the following section to your package.json:
.eslintrc. Add more hooks if you need to, even post-commit or pre-push ones.
Note: As of writing (25/11/2020), husky won’t install the hooks due to an issue with npm v7. Use npm v6 or use another way to install the hooks (e.g. https://pre-commit.com/).
🔄 Set up hot reload
Last but not least of our QoL improvements, we want to set up hot reload so that the server reloads automatically when we edit some code. Let’s install nodemon:
npm i -D nodemon ts-node
Add the following script to the package.json:
npm run start to start the server. Now, each modification you make to the source code will automatically reload the server in a few seconds. Pretty handy!
Part 1? Done. We built a basic server that can handle HTTP routing, and we are ready to write some clean and strongly-typed code. Yay! 🎊
However we still need many other tools to provide features like an API (graphQL) that will allow accessing a database (MongoDB). Adding these tools will make the server hard to deploy, so containerization will also be useful. Click the link below to jump to part II to set these things up.