Node API Gateway
This is a NodeJS Microservice API Gateway, built using Traefik, NATS, Hapi, Hemera and Docker.
The structure of this project allows us to easily scale workers in seconds. NATS is used as the "nervous system" for the distributed system removing the need for service-discovery, and load-balancing of our hemera-services. Traefik performs load-balancing on the API Gateway itself.
Getting started
- It is recommended that you download and install VS Code.
- Clone the repository
- Install Docker
- Running the system:
-
- Execute
docker-compose up
from the project root directory.
- Execute
- Stopping the system:
-
- Execute
docker-compose stop
from the project root directory.
- Execute
- Destroying the system:
-
- Execute
docker-compose down
from the project root directory.
- Execute
-
- This will remove the containers that were created
- Use the Postman file in
./system/postman
to test API requests.
Bonus
Need a React frontend or a Kubernetes deployment? See below!
React
A React frontend has been developed and can be found at react.antd.fuse
Kubernetes
Kubernetes scripts for this project and can be found at kubernetes.api.gateway
Development
To add a new microservice:
- Use the Template Service as a starting point.
- Copy and paste the folder, and rename all of the
template
references. - Add your routes to the API Gateway.
- Bring the system up by executing
docker-compose up
- You should now be able to use
npm start
from your microservices' directory. - Once you are done with development add an entry for the new microservice to the
docker-compose.yml
file.
Scaling
To create additional containers to assist with handling high load you can execute docker-compose scale auth-service=5 api-gateway=2
, after the system is up.
When scaling you must remove the container_name, e.g. (container_name: lupinemoon-auth-service
), and debugging configuration from the docker configuration file, e.g. docker-compose.yml
Note: In production we use an container orchestration tool like Kubernetes / Mesos
Project structure
The project is divided into various microservices. A microservice in this architecture is simply a service which is responsible for a single broadly-defined task, e.g. the auth-service
performs the task of creating, and validating JWT tokens against a database of authorized users.
Microservices
- API Gateway - This is the central REST entrypoint into the application for external consumers.
- Auth Service - The Auth Service is responsible for authorizing users and validating JWT tokens. All requests except
login
andsign-up
, must be decorated withconfig: { auth: 'jwt' }
when declaring the routes in theapi-gateway
. - Lupine Moon DB - This database is used by all of the other microservices that need to access the central database.
- Cache Service - Is responsible for caching
GET
requests for all of the microservices. - Registration Service - The Registration Service is responsible for performing registration related, e.g. early access sign-up, contact us form submission, sign-up, etc. All new requests must be decorated with
config: { auth: 'jwt' }
when declaring the routes in theapi-gateway
. - Redis - Serves as the data store for cached results.
- NATS - The central "nervous system" of the application.
- Jaeger - Provides distributed tracing of requests.
- Traefik - A reverse proxy and load balancer that wraps all of the microservices, to provide easy deployment (Docker / Kubernetes), security (SSL), auto discovery (Microservices), and tracing (Jaeger).
API Gateway
The API Gateway serves as the entrypoint for external consumers of the REST API. The Gateway itself is not responsible for performing logical work, but simply routes traffic to one of the pre-defined microservices to retrieve a result for a consumer.
Directory structure:
src
├── environments
│ ├── config.default.json
│ ├── config.development.json
│ ├── config.production.json
│ ├── config.staging.json
│ └── index.ts
├── index.ts
├── logger
│ └── app.logger.ts
├── routes
│ ├── auth.routes.ts
└── types
└── ...
Overview:
- environments - Configuration of static application variables. See Configuration for more information and usage.
- index.ts - Application entrypoint.
- logger - Application logging.
- routes - Hapi route definitions.
- types - Additional typescript definitions.
Configuration
The configuration of static application variables is done using nconf. It allows us to set the hierarchy for different sources of configuration with no defaults. Thus providing an easy way to configure the applications for the various environments, i.e. development
, staging
, and production
.
The configuration hierarchy being used:
- argv - Command line arguments will override environment variables, and configuration files.
- env - Environment variables will override configuration files.
- file - Files will be used as the baseline configuration if neither of the above is specified.
Auth Service
The Auth Service is responsible for authorizing users and validating JWT tokens. All requests except login
and sign-up
, must be decorated with config: { auth: 'jwt' }
when declaring the route in the api-gateway
.
Directory structure:
src
├── actions
│ ├── auth.actions.ts
│ └── index.ts
├── app
│ └── database.app.ts
├── environments
│ ├── config.default.json
│ ├── config.development.json
│ ├── config.production.json
│ ├── config.staging.json
│ └── index.ts
├── index.ts
├── logger
│ └── app.logger.ts
├── logic
│ └── shared.logic.ts
├── models
│ ├── internal
│ │ ├── search-term.internal.ts
│ │ └── token.internal.ts
│ ├── options
│ │ └── search-query-builder.options.ts
│ ├── role.model.ts
│ └── user.model.ts
├── repositories
│ ├── base.repository.ts
│ └── user.repository.ts
├── services
│ ├── base.service.ts
│ └── user.service.ts
├── types
│ ├── ...
└── utils
├── auth.utils.ts
└── system.utils.ts
Lupine Moon DB
This PostgreSQL database is used by all of the microservices that require access to the central Lupine Moon database. Each microservice that needs to access this database should do so via a specific schema, e.g. the registration-service
uses the registration
schema
Directory structure:
lupinemoon-db
├── db-data
├── db-scripts
│ ├── create.sql
│ └── seed.sql
└── docker-compose.yml
Cache Service
Is responsible for caching GET
requests for all of the microservices in Redis. It registers via Hemera and is therefore open to communicate with all of the other microservices.
Directory structure:
src
├── index.ts
└── types
└── ...
Registration Service
This microservice is used all registration events that occur on the front-end, e.g. early access sign-up, user contact form, registration, etc.
Directory structure:
src
├── actions
│ ├── early-access.actions.ts
│ ├── index.ts
│ └── metrics.actions.ts
├── app
│ └── database.app.ts
├── environments
│ ├── config.default.json
│ ├── config.development.json
│ ├── config.production.json
│ ├── config.staging.json
│ └── index.ts
├── index.ts
├── logger
│ └── app.logger.ts
├── logic
│ └── shared.logic.ts
├── models
│ ├── early-access.model.ts
│ ├── internal
│ │ └── search-term.internal.ts
│ └── options
│ └── search-query-builder.options.ts
├── repositories
│ ├── base.repository.ts
│ └── early-access.repository.ts
├── services
│ ├── base.service.ts
│ └── early-access.service.ts
└── types
└── ...
Captcha
Captcha codes have been integrated using reCaptcha. Register for v2 reCaptcha codes here, then change the secrets in the environment variables accordingly.
"RECAPTCHA_SECRET": "<YOUR-SITE-KEY>"
"RECAPTCHA_INVISIBLE_SECRET": "<YOUR-SITE-INVISIBLE-KEY>"
Redis
Redis serves primarily as the data store for caching GET
request results of all of the microservices (individual configuration is required).
Overview:
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster
NATS
The central "nervous system" of the application.
Directory structure:
nats
├── Dockerfile
├── entrypoint.sh
└── gnatsd.conf
Overview:
NATS is a family of open source products that are tightly integrated but can be deployed independently. NATS is being deployed globally by thousands of companies, spanning innovative use-cases including: Mobile apps, Microservices and Cloud Native, and IoT. NATS is also available as a hosted solution, NATS Cloud
The core NATS Server acts as a central nervous system for building distributed applications.
Template Service
This should be used as a quick start when creating a new microservice.
Directory structure:
src
├── actions
│ ├── index.ts
│ └── template.actions.ts
├── environments
│ ├── config.default.json
│ ├── config.development.json
│ ├── config.production.json
│ ├── config.staging.json
│ └── index.ts
├── index.ts
├── logger
│ └── app.logger.ts
├── logic
│ └── shared.logic.ts
└── types
└── ...
Jaeger
Provides distributed tracing of requests.
Overview:
Jaeger, inspired by Dapper and OpenZipkin, is a distributed tracing system released as open source by Uber Technologies. It is used for monitoring and troubleshooting microservices-based distributed systems, including:
- Distributed context propagation
- Distributed transaction monitoring
- Root cause analysis
- Service dependency analysis
- Performance / latency optimization
Traefik
A reverse proxy and load balancer that wraps all of the microservices, to provide easy deployment (Docker / Kubernetes), security (SSL), auto discovery (Microservices), and tracing (Jaeger).
Directory structure:
traefik
└── traefik.toml
Overview:
Traefik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. Traefik integrates with your existing infrastructure components (Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS, ...) and configures itself automatically and dynamically. Pointing Traefik at your orchestrator should be the only configuration step you need.
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul). Now you want users to access these microservices, and you need a reverse proxy.
Traditional reverse-proxies require that you configure each route that will connect paths and subdomains to each microservice. In an environment where you add, remove, kill, upgrade, or scale your services many times a day, the task of keeping the routes up to date becomes tedious.
This is when Traefik can help you!
Traefik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
Run Traefik and let it do the work for you! (But if you'd rather configure some of your routes manually, Traefik supports that too!)
Features:
- Continuously updates its configuration (No restarts!)
- Supports multiple load balancing algorithms
- Provides HTTPS to your microservices by leveraging Let's Encrypt (wildcard certificates support)
- Circuit breakers, retry
- High Availability with cluster mode (beta)
- See the magic through its clean web UI
- Websocket, HTTP/2, GRPC ready
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
- Keeps access logs (JSON, CLF)
- Fast
- Exposes a Rest API
- Packaged as a single binary file, and available as a tiny official docker image
Supported Providers:
- Docker / Swarm mode
- Kubernetes
- Mesos / Marathon
- Rancher (API, Metadata)
- Azure Service Fabric
- Consul Catalog
- Consul / Etcd / Zookeeper / BoltDB
- Eureka
- Amazon ECS
- Amazon DynamoDB
- File
- Rest
System (Directory)
The system directory contains postman files, and load testing scripts.
Directory structure:
system
├── load-test
│ └── load-test.yml
└── postman
└── node.api.gateway.postman_collection.json
Major Technologies used
Debugging with VS Code and Docker
It is possible to debug code that is running in a docker container by following the steps below:
- Add a new debug configuration to VS Code by opening
./.vscode/launch.json
, e.g.
{
"name": "Attach to Docker - Auth Service",
"type": "node",
"protocol": "inspector",
"request": "attach",
"restart": true,
"address": "localhost",
"sourceMaps": true,
"skipFiles": ["node_modules"],
"port": 9229,
"localRoot": "${workspaceRoot}/auth-service/",
"remoteRoot": "/home/app/node"
}
- Edit the docker configuration file in order to enable debugging on the running Node.js process, e.g. Add the following the service definition:
ports:
- 9229:9229
command: node --inspect=0.0.0.0:9229 /home/app/node/dist/index.js
Full example:
auth-service:
build:
context: "../../auth-service"
links:
- nats
- postgres-lupinemoon
depends_on:
- nats
- redis
- jaeger
restart: always
container_name: lupinemoon-auth-service
ports:
- 9229:9229
command: node --inspect=0.0.0.0:9229 /home/app/node/dist/index.js
environment:
NODE_ENV: staging
NATS_URL: nats://nats:4222
NATS_USER: ruser
NATS_PW: T0pS3cr3t
JAEGER_URL: jaeger
DB_HOST: postgres-lupinemoon
HEMERA_LOG_LEVEL: debug
networks:
- node-ms-net
- Add a breakpoint at the required place on a file in the
dist
directory.
Dashboards
- Traefik http://localhost:8183/
- NATS http://localhost:8222/
- Jaeger http://localhost:16686/
Load Testing
Artillery is a modern, powerful & easy-to-use load testing toolkit. Use it to ship scalable applications that stay performant & resilient under high load.
npm install -g artillery
cd system/load-test
artillery run -o artillery_report_load_test_1.json load-test.yml
artillery report artillery_report_load_test_1.json