PostgreSQL, Node, and Koa
This acts as a guide for creating a relatively production ready backend micro-service, which can be customized to your needs. It already has error handling, logging, observability, rate limiting, graceful shutdown, a test file, and Docker optimizations.
The reference template repository can be found at irmerk/pnk-stack
All of this page assumes that you are using a Mac and that your software is up to date. If not up to date you may run into install issues when you install the packages below.
macOS now ships with Zsh as the default shell. If you want more customization and handy shortcuts, you can install oh my zsh.
You can use the Terminal app that ships with macOS, but iTerm2 is a popular and recommended alternative with more features and following. For information regarding prompt customization, see Zsh Prompt.
A helpful setting can also allow you to use Alt/Cmd + Right/Left Arrow in iTerm.
Use whatever text editor / integrated development environment (IDE) you prefer. At the time of this writing, Visual Studio Code is an extremely popular one with a built-in plugin manager for easily extending and customizing functionality.
For helpful VSCode keyboard shortcuts to incorporate into your workflow, see their Tips and Tricks.
My suggestions for VSCode extensions and keyboard shortcuts.
Install the Homebrew package manager for easy and relatively safe installation and upgrades.
Recommended tools:
Manages installations of Node.js and npm:
brew install nvm \
&& mkdir ~/.nvm \
&& echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zprofile \
&& echo '[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" # This loads nvm' >> ~/.zprofile \
&& source ~/.zprofile
Node.js and npm could be installed directly via Homebrew as well, but doing so makes it challenging to switch between versions. Some projects each require specific versions of Node.js and npm versions to work properly, so nvm installations are strongly preferred.
nvm install node
brew install git
Set your name and email address explicitly:
git config --global user.name "<YOUR_NAME>"
git config --global user.email <YOUR_EMAIL>
- I chose Koa because it is newer, less opinionated, lighter weight, and easier to customize than the standard of Express. Koa is modular and customizable because it requires modules for routing, the templating engine, and JSONP. This way, I can create middleware ourselves or use the built-in middleware.
- Koa focuses more on modern features of the JavaScript ES6 language, like generators, async functions, and the Node.js runtime. Moreover, it uses promise-based flows and async-await syntax to get rid of callbacks, making the code more manageable, cleaner, and more readable. Because it uses generator syntax instead of callbacks, I can use the
yield
keyword to exit and then reenter. - Koa uses a
context
object, which encapsulatesreq/res
objects into one, helping us develop more efficiently by using several helpful methods. Finally, Koa uses cascading middleware. Thanks to asynchronous functions, middleware will run in a cascading fashion until the last middleware is reached.
- HTTP Client Library for Node.js
- Comparison to other libraries
- I chose axios instead of got because “[the got] package is native ESM and no longer provides a CommonJS export. If your project uses CommonJS, you will have to convert to ESM or use the dynamic
import()
function. Please don't open issues for questions regarding CommonJS / ESM.” - GOT has issues with linting, TypeScript, ESM/CJS, and JSON.
The logging transport is configured for logging to the console. This happening in a service set up in a cloud service can be configured to be picked up from STDOUT
Why I add a new line to the end of a file.
npx
: When I run a command with npx
, it will check whether the command exists in the local node_modules/.bin
directory or in the system-wide installed packages. If it doesn't find the command, it will attempt to temporary install the package and execute it. This makes npx
an ideal tool to run the locally installed service being called.
Dockerfile
- Dockerizing a service allows for it to behave the same regardless of the platform on which it is run. A Dockerfile is a blueprint on which the Docker image is built. When the built image is running, it is called a container. Essentially, the container starts as a Dockerfile that has the instructions on how to build the Docker image. The same image can be used to spin up one or even hundreds of containers, which is why Docker is so useful for software scalability.
- I am using a slim production stage and a more feature-rich, development-focused dev stage.
- Setting
NODE_ENV
toproduction
can increase performance by 3x. - Notice some of our commands are put together with an
&&
, creating fewer Docker layers, which is good for build caching. - Debian-based images because it may be better in some cases than the more standard alpine linux image.
docker-compose.yml
- Docker Compose is a way to more easily setup multiple services running with Docker in an ecosystem. The big advantage of using Compose is you can define your application stack in a file, keep it at the root of your project repo (it’s now version controlled).
.dockerignore
- Just like I wouldn’t use Git without .
gitignore
, I use a.dockerignore
file to ignore files that I don’t want to land in our Docker image. It helps to keep the Docker image small and keep the build cache more efficient by ignoring irrelevant file changes.
- Just like I wouldn’t use Git without .
dumb-init
- I use
dumb-init
because it will be the first process (PID 1
) and will handle the signal forwarding and the reaping of the zombie processes. It is a simple process supervisor and init system that helps to handle these situations. - Docker containers usually run a single application, and for that application, it is normal to expect it to receive signals from the Linux environment. However, when running in the background (as a daemon), the application may not correctly handle the Linux signals, which can lead to zombie processes and not graceful termination.
- I use
- Multiple Stages
- Optimizing layers with multiple stages (build and production) in the
Dockerfile
results in a drastically smaller image size:
- Optimizing layers with multiple stages (build and production) in the
REPOSITORY TAG IMAGE ID CREATED SIZE
single-stage latest 8ceb85286547 4 seconds ago 402MB
multi-stage <none> ec0680eefe42 About a minute ago 300MB
- emmanuelnk/RESTful-Typescript-Koa
- posquit0/koa-rest-api-boilerplate
- inadarei/maikai
- banzaicloud/service-tools
- [transitive-bullshit/koa-micro][transitive]
- Koa Js : Part 1 - How to make a Koa server in 10 minutes!
- Sentry's Koa Guide
- Health check
- Docker