Note
This is the predecessor of this project. Hop over and have a look. It comes with TypeScript, ESM, tsdown Rust bundler, and c8 native V8 code coverage beyond other cutting-edge tools and approaches.
Some formatting settings like format on save using the ESLint plugin for VSCode:
{
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
],
"files.insertFinalNewline": true
}To support direnv, everything you need to do is to define an .envrc that calls dotenv as described here.
The Fastify web framework comes bundled with Pino by default, as described here. A pretty-printed variant can be achieved by piping stdout to pino-pretty:
npm i -g pino-pretty
npm run dev | pino-prettyJay Wolfe has some nice blog posts about using Fastify the right way:
- Setup Your Fastify Server With Logging The Right Way - No More Express
- Setup A Fastify App with Jest Tests the Right Way
To make use of the coverage report it’s recommended to install the Jest plugin.
# run tests npm test # run tests and generate coverage report npm run test:coverage open coverage/lcov-report/index.html
# clean compile folder npm run clean # compile TypeScript to JavaScript npm run build # run application npm start # run application in development mode with hot-reloading npm run dev # lint sources npm run lint # format sources npm run fmt
Environment variables
| Variable | Type | Default |
|---|---|---|
LOG_LEVEL |
string |
info |
PORT |
number |
4000 |
HAProxy is a free and open source software that provides a high availability load balancer and reverse proxy for TCP and HTTP-based applications that spreads requests across multiple servers.
# start 3 server instances
PORT=4001 npm start
PORT=4002 npm start
PORT=4003 npm start# install and start HAProxy
brew install haproxy
haproxy -f ./haproxy/haproxy.cfg# open the stats dashboard open http://localhost:4000/admin/stats # call the API curl -i http://localhost:4000 # terminate one of the API servers with `kill <pid>` # HAProxy detects that the API is down # re-start the API server and HAProxy will include it into load-balancing again
By default a health check is performed on Layer 4 (TCP). If haproxy.cfg defines option httpchk GET /health for a backend the health check is changing to be on Layer 7 (HTTP), as you can see in the stats dashboard (LastChk column).
In order to use gzip compression, you need to provide the respective header. The HAProxy configuration file defines the compression to be available for content types application/json and text/plain.
curl -s http://localhost:4000/ -H "Accept-Encoding: gzip" | gunzip
HAProxy can also be configured to close connections as soon as a maximum number of connections is reached to avoid back pressure.
In order to load test one instance of the application, we stop HAProxy and start the instance without piping to pino-pretty.
wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CPU.
brew install wrk
$ wrk -c10 -d60s --latency http://localhost:4001/health Running 1m test @ http://localhost:4001/health 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 752.68us 0.85ms 33.52ms 93.70% Req/Sec 7.73k 0.94k 9.52k 74.42% Latency Distribution 50% 430.00us 75% 0.93ms 90% 1.32ms 99% 3.45ms 923474 requests in 1.00m, 108.33MB read Requests/sec: 15388.55 Transfer/sec: 1.81MB
$ haproxy -f ./haproxy/passthrough.cfg $ wrk -c10 -d60s --latency http://localhost:4000/health Running 1m test @ http://localhost:4000/health 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 0.90ms 1.54ms 49.85ms 90.55% Req/Sec 10.08k 2.29k 13.40k 66.08% Latency Distribution 50% 347.00us 75% 689.00us 90% 2.33ms 99% 6.89ms 1203917 requests in 1.00m, 141.22MB read Requests/sec: 20062.58 Transfer/sec: 2.35MB
Based on these numbers the application – run locally on my specific machine – reaches a throughput of ~15,000 requests/s and a TP99 (top percentile 99%) for latency of ~4 ms, when testing only one instance. Using HAProxy with a passthrough configuration reaches a throughput of ~20,000 requests/s and a TP99 for latency is ~7 ms.
The Three Pillars of observability are metrics, logs, and traces.
- Metrics: Prometheus, Grafana Mimir for multi-tenant, long-term storage for Prometheus
- Logs: Grafana Loki
- Traces: Grafana Tempo
# install plugin to allow applications to ship their logs to Loki docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions # start observability stack docker compose up -d # stop observability stack docker compose down # stop observability stack and remove volumes docker compose down -v
In Grafana we can query for logs from the Loki container that contain a traceID using this query:
{container_name="m99coder-loki"} |= "traceID"
Drop down any log line of the result and click the "Tempo" link to jump directly from logs to traces.
npm i pino-tee -g npm start | pino-tee info ./logs/api.info.log | tee -a ./logs/api.log | pino-pretty
You can see the application logs in Grafana.
Note: So far the log files are not kept in sync between the host and the container, which results in no updates in Grafana.
- Run Grafana Docker image
- Install Grafana Loki with Docker or Docker Compose
- Getting started with Grafana Loki: Guide, Code, Scraping
- Grafana Tempo: Documentation, Loki example
- A simple Loki setup with Grafana
- Loki logging in Node.js using Fastify and Pino
- OpenTelemetry – Grafana Demo