This project is meant to be boilerplate for creating APIs in Node. This document will explain the overall structure of the project, as well as some of the design decisions. It will also highlight some of the processes and tools used in the project.
- Structure
- Building
- Testing
- Configuration
- Local Development
- Logging
- API Documentation
- Docker
- Semantic Releases
- CI
- IDE Integration
The project utilizes a modified form of the traditional controller --> service --> repository architecture. Below is a brief description of the sub-directories under src.
Houses the application's configuration. The environment.ts file is where environment variables are centralized and constructed into a unified global configuration object (uses node-convict). If the NODE_ENV
environment variable is set to development
(the default) then the application will attempt to load extra configuration values from a file named env.json
in the root of the project. This is useful for easily injecting configuration values into the application during development.
Much like a repository, but instead of abstracting persistence it abstracts an API.
A handler is where the API request first lands when it hits the application. In this architecture handlers take the place of controllers. Ideally a handler only includes "application" logic. "Business" logic is the provenance of the service. The handler also serves as the "break" between the framework and the underlying code.
Hooks are a framework feature of Hapi. They allow us to intercept the request/response during its lifecycle and perform logic/modifications. In the context of this template they are used exclusively for logging. You can read more about them here
A sort of umbrella for shared modules of code that don't belong in any of the other directories.
There are two types of models for this template: Joi models and Typescript interfaces. Joi is a Node.js validation library written to be used with Hapi. It is a very robust and convenient solution for request/response validation. Typescript interfaces describe the structure and type of entities in the code. There is some obvious overlap/duplication between the two tools, but that is balanced by the benefit they provide.
An abstraction over your persistence layer.
Configuration for API endpoints. The definition of a route is dicated by Hapi. See here for more details.
The middleware between handler and repository/gateway. Performs business logic and orchestrates the request.
Entrypoint for the application. Starts server, registers routes, loads plugins, etc.
npm run build
Webpack is used to compile/bundle the application. Webpack's main configuration file is webpack.config.js. If the NODE_ENV
environment variable is set to production
, Webpack will bundle the application in "production" mode (minify, uglify, etc), otherwise it will skip the extra "production" bundling steps. Webpack places the bundled application in the dist
directory. A quick note on Webpack and external dependencies (node_modules): You can have Webpack bundle your dependencies in with your application. This helps tree-shake your application code and keep your bundle size small. However, in many cases it is also a more complex build configuration. For this reason the template does not bundle external dependencies with the app, but rather assumes that the dependencies exist outside the app. If for some reason you are concerned about the bundle size, consider performing the extra configuration to bundle external dependencies.
Run all available tests
npm run test:all
npm run test
Unit tests are located in the test directory. The Mocha test runner, Chai assertion library, and Sinon mock/stub library are used to perform unit tests. I use the Mocha-webpack library to build/compile tests. Its main configuration files are mocha-webpack.opts and webpack.config.test.js.
npm run test:coverage
The Istanbul library is used to calculate and check test coverage. Its main configuration file is .nycrc. I have configured Istanbul to calculate and display test coverage, but not fail builds for low test coverage. To start failing builds for test coverage thresholds, toggle the check-coverage
property in .nycrc to be true
.
npm run test:lint
TSLint is used for linting. Its main configuration file is tslint.json
npm run test:security
npm audit is used to scan the application's external dependencies (node_modules) for known vulnerabilities. The test saves its results as a CI artifact, but does not fail the build. This is due to a limitation of npm audit
not allowing exceptions for vulnerabilities.
Key | Description | Default Value |
---|---|---|
APP_NAME | Name of application | 'node-hapi-typescript-template' |
ENABLE_LOGS | Flag to enable/disbale logging | true |
ENV_FILE_PATH | Path to file with extra environmental configuration values - reference | project_root/env.json |
HOST | Hostname or IP address the server will listen on | '0.0.0.0' |
LOG_LEVEL | Log level for application ('info', 'debug', etc) | 'info' |
NODE_ENV | Environment where application is running | 'development' |
PORT | Port the server will listen on | 8000 |
PROTOCOL | PROTOCOL used, must be one of ['http', 'https', 'socket'] | 'http' |
Build and start the application
npm run dev
Build and start the application and reload when a change is detected
npm run dev:watch
Bunyan is used for logging. All logs are written to stdout. The log level is configured with the LOG_LEVEL
environment variable (default is 'info'). There is also a convenience configuration to turn off logging completely (mostly used during testing to avoid spurious logging). Toggle this config by setting the ENABLE_LOGS
environment variable to true
or false
(default is true
). Bunyan formats logs into a JSON structure that is easily parsed by a log aggregator. For local development, I have configured Bunyan to write logs in a more human readable format using bunyan-debug-stream. Logs are written in the human readable format if the NODE_ENV
environment variable is set to development
(the default) or unit_test
, otherwise logs are written in JSON format.
The hapi-swagger library is used to generate Swagger API documentation with the information in the route configuration (path, method, request/response schema, etc). The documentation can be accessed via the application at /documentation
.
Run application locally in Docker
npm run docker
Build Docker image
npm run docker:build
Push Docker image
npm run docker:push
Must have Docker installed to run the above commands.
This template follows SemVer. The semantic-release library is used to manage version numbers, cut GitHub releases, and create/update the changelog
This template follows the commit message guidlines outlined by the Angular project. The commit message format determines the release number and is enforced with a pre-commit hook (implemented via husky). Here is an example of the release type that will be created based on the commit messages:
Commit message | Release type |
---|---|
fix(pencil): stop graphite breaking when too much pressure applied |
Patch Release |
feat(pencil): add 'graphiteWidth' option |
|
perf(pencil): remove graphiteWidth option BREAKING CHANGE: The graphiteWidth option has been removed. The default graphite width of 10mm is always used for performance reasons. |
This template is configured to use CircleCI. All tests are configured to write their results to the artifacts
directory. The CircleCI workflow is configured to upload this directory as a pipeline artifact.
Editorconfig is used to unify code formatting (indention, spacing, etc) among multiple developers. Editorconfig is supported by many popular IDEs. For VSCode you can download the extension here.
TSLint is used for linting. For VSCode you can download the extension here.
This template is configured to use the debugging features in VSCode. There are runtime options to launch your application when debugging: "Launch in Node" and "Launch in Docker". Both work as expected and require that Node and Docker respectively be installed on the system. All of the configuration necessary to set this up is in the .vscode directory.