Project to demonstrate a small REST-based service to retrieve weather information in different cities using the following technologies:
- Node.js;
- Docker (including Docker Compose);
- Unit tests (in the same context of REST-based service);
- Integration tests (in a separated container).
Main project structure:
.
├── app
│ ├── Dockerfile
│ ├── configs
│ ├── controllers
│ ├── lib
│ ├── resources
│ ├── routes
│ ├── services
│ ├── tests
│ │ └── unit
│ └── validations
├── docker-compose.yml
└── integration
├── Dockerfile
└── features
git clone https://github.com/isena/demo-node-docker-test.git
In app
directory:
npm install
cp .env.example .env
The weather endpoint uses data from [openweathermap.org]. Go to [openweathermap.org/api], subscribe (for free) and get an API Key to replace <<<API_KEY_VALUE_HERE>>>
in the following command:
export APP_WEATHER_API_KEY="<<<API_KEY_VALUE_HERE>>>"
npm start
To monitor changes and restart automatically:
npm run start:watch
And you are ready! Visit some city to check, e.g., http://localhost:5000/cities/2873891
In app
directory:
npm test
To monitor changes and restart automatically:
npm run test:watch
In integration
directory:
npm install
When running locally, set APP_HOST
:
export APP_HOST="localhost"
With the service running, run the following command:
npm test
To monitor changes and restart automatically:
npm run test:watch
This may be the simplest way to run the project. After setting the environment variables, just run the following command:
docker-compose up
and the service will be started and the integration tests will be executed.
In app
directory:
- Build:
docker build -t demo-weather:0.0.1 .
- Run
export APP_WEATHER_API_KEY="<<<API_KEY_VALUE_HERE>>>"
export APP_HOST="demo-weather-api"
docker run \
--env APP_WEATHER_API_KEY=$APP_WEATHER_API_KEY \
-p 5000:5000 \
--name $APP_HOST \
-ti --rm demo-weather:0.0.1
In integration
directory:
- Build:
docker build -t demo-weather-integration-tests:0.0.1 .
- Run
export APP_HOST="demo-weather-api"
docker run \
--env APP_HOST=$APP_HOST \
--name integration \
--link $APP_HOST:integration \
-ti --rm demo-weather-integration-tests:0.0.1
HTTP 200 Ok
with body:
➜ ~ curl http://localhost:5000/cities\?lat\=49.48\&lng\=8.46
[{"id":2864869,"name":"Neuhofen","country":"DE","coord":{"lon":8.42472,"lat":49.42778}},...]
HTTP 400 Bad Request
if parameters are missing:
➜ ~ curl http://localhost:5000/cities\?lat\=\&lng\=
{"status":"error","message":"lat/lng required","code":"BadRequestError"}
HTTP 200 Ok
with body:
➜ ~ curl http://localhost:5000/cities/2873891
{"id":2873891,"name":"Mannheim","country":"DE","coord":{"lon":8.46472,"lat":49.488331}}
HTTP 404 Not Found
if thecity_id
was not found:
➜ ~ curl http://localhost:5000/cities/99999999
{"status":"error","message":"not found","code":"NotFoundError"}
HTTP 200 Ok
with body:
➜ ~ curl http://localhost:5000/cities/2867310/weather
{"coord":{"lon":8.36,"lat":49.44},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":288.15,"pressure":1017,"humidity":87,"temp_min":285.93,"temp_max":290.37},"visibility":10000,"wind":{"speed":3.6,"deg":210},"clouds":{"all":0},"dt":1565567145,"sys":{"type":1,"id":1265,"message":0.0076,"country":"DE","sunrise":1565583171,"sunset":1565635839},"timezone":7200,"id":2867310,"name":"Mutterstadt","cod":200}
HTTP 404 Not Found
if thecity_id
was not found:
➜ ~ curl http://localhost:5000/cities/99999999/weather
{"status":"error","message":"not found","code":"NotFoundError"}
- Enable security protocol (TLS);
- Cover with more unit tests (and measure code coverage);
- Add a tool for endpoint documentation/specification (e.g. Swagger);
- Load cities dynamically. The current code loads the cities through a local json file and uses
require
that runs synchronously; - Add semantic versioning;
- Node.js: handle multiple CPUs;
- Docker: use multi-stage builds.
- restify: web service framework for building RESTful web services;
- winston: logging library;
- joi: Object schema validation;
- standard: JavaScript style guide, linter, and formatter;
- dotenv: module that loads environment variables from a
.env
file intoprocess.env
; - cucumber: tool for running automated tests written in plain language. We use here for integration tests;
- jest: JavaScript testing framework. Used here for unit testing;
- geolib: Library to provide basic geospatial operations. Although the formula used to measure the distance between two geospatial coordinates is not complex (see Haversine formula: https://en.wikipedia.org/wiki/Haversine_formula), this robust library already abstract the calculations. "Why would we reinvent the wheel?".