Imagine you are writing a NodeJS webserver using expressjs. What should you do to create a secure, robust, easy to debug and deploy server? Here are my steps.
package.json
Always create a package.json
file, even if you do not intend to make the module public.
This file will list all dependencies (rather than listing them manually in the README.md
),
can have script commands to test and run. Inside your folder run npm init
and answer a couple
of questions. If the server code is private and you want to avoid accidentally publishing it
to the public registry, set the private
property
1 | { |
use configuration helper
To avoid messing with per-environment (local, unit testing, staging, production) settings, start using a helper config module right away. I recommend nconf or config.
At a very least, read the port from the environment first, otherwise use default
1 | var app = express(); |
Hosting environments will pass the port to listen to via the environment variable, while in the local environment is ok to hardcode it.
automatically restart server during development
I use nodemon to start the server while developing it locally.
1 | "scripts": { |
While developing locally I use npm run watch
. Anytime there is a file change or a crash, the server
restarts automatically. This speeds up the development.
save exact dependency versions
I prefer avoiding fuzzy dependency versions, like '1.1.*', or '^2.0.1', instead prefer to save
the exact version of the installed module's in the package.json
. To always do this, create and
set the following configuration in a new project's .npmrc
file
save-exact=true
Here is my .npmrc
file in my user folder
email=<my NPM email>
save-exact=true
fetch-retries=5
always-auth=true
advanced: clean up package.json
To make sure the package.json
sets all the options and everything is written alphabetically,
I recommend using grunt-nice-package. Even if you
do not use grunt, you can still easily run it
npm install --save-dev grunty grunt-nice-package
Add the command to the scripts
list
1 | "scripts": { |
Execute npm run nice-package
and fix all the errors / warnings. You package.json
surely looks nice now!
use a linter
To catch simple errors (like misspelled variables) in the static code without writing unit tests, install a linter, I recommend eslint - it is versatile, powerful and can be easily extended with custom rules.
npm install --save-dev eslint
Then create a file .eslintrc
- and set the following starting JSON
1 | { |
You can read how to configure eslint
and see the entire list of rules at [http://eslint.org/docs/rules/](http://eslint.org/docs/rules/.
To run eslint
create a new command the package.json
scripts object - this allows to use shortcut name eslint
1 | "scripts": { |
If fixing every error is too difficult initially, at least make them warnings write in the source code, for example, to make unused variables a warning, rather than an error, use the comment
1 | /* eslint no-unused-vars: 1 */ |
advanced: lint on each commit
To always lint before committing the code to avoid breaking the master, set up a pre-commit command.
npm install --save-dev pre-git
Then add all commands (only lint for now) to the package.json
1 | "pre-commit": "npm run lint" |
You can always skip the pre-commit
hook by committing with -n
flag.
configure continuous integration service
Use Codeship (fast, works with Github and BitBucket, 100 private builds each month) or CircleCI (very fast, works with Github, single build host for free, even for unlimited private builds) to build your project on each commit.
Add badges to the project's README file to show the current build status, see badges
deploy to a hosting from CI
Instead of manually pushing the code to the hosting service, configure the CI server to push automatically.
All CI services worth their salt have nice and simple to configure integration with the major providers
(AWS, Heroku, etc). Just enter the branch / your hosting API key / application name. Each commit will be tested
and if the tests pass, deployed to the host. This is fine, at least for the master
branch.
embed git commit id
It is very useful for debugging to embed the last commit id in the server. The server can print the commit id at startup, add it as a meta tag to the rendered HTML views and send with each exception to the crash reporting service. You can read how to pass the commit id from the CI to the hosting environment. Then you can embed the commit as a meta tag in each rendered view or pass to the crash reporting service Raygun.
setup crash reporting service
You MUST setup backend and client-side crash reporting service, like Sentry or Raygun. The configuration is very simple, and there are handlers for both global JavaScript exceptions and errors inside the ExpressJS middleware. You will discover how the software crashes in the way you have never suspected. If you need to configure Sentry server-side, you can use raven-express middleware.
I advise to configure separate environments for the development vs production, and blacklist the local development to avoid the noise of multiple local errors.
To verify that the crash reporting service is working and catches the errors as expected,
you can include the crasher middleware. It throws an artificial
synchronous and asynchronous exceptions whenever someone requests /api/crash
route. This is extremely
useful for quick confirmation that the errors thrown inside the middleware are reported, as are
asynchronous global errors.
output pretty HTML
I strongly believe that the server should always generate pretty HTML output, any space savings between the minified and the original HTML text are negligible. Full indented HTML source is invaluable when looking through the page trying to understand the structure. To enable pretty HTML rendering in ExpressJS 4 just add the following after setting the render engine.
1 | var app = express(); |
setup simple unit tests
It is important to start unit testing very quickly. I show how to test the ExpressJS server in this blog post.
secure the server against common attacks
Read this excellent series of blog posts and apply as many solutions to secure your server against injection / cross site and other types of attacks. The simplest solution that covers most of the recommended steps is to include the helmet middleware.
1 | var express = require('express'); |
Protect against stray API requests
This is for websites that use forms to submit data.
If someone steals the session cookie from a valid session, they can then execute API requests directly to your server. A good intro to these "cross site forgery" attacks can be found at Understanding CSRF
To protect yourself, you want to send something else besides the cookie with each API request to guarantee that it is coming from your own web page, called CSRF tokens.
We can use expressjs/csurf module to automatically create a token that we can verify when someone makes a POST request.
1 | // this example uses cookies |
The form in the send
view has the csrf token value in a hidden input
field
1 | <form action="/process" method="POST"> |
End to end testing
I like unit testing Express but like end 2 end testing the server even more. Cypress.io is an excellent and powerful tool that works quickly right away. To start the server and then run the tests I use a utility to run NPM script commands in parallel.
1 | npm install --save-dev npm-run-all |
The script commands (see actual example in this repo).
1 | { |
More information about making an ExpressJS application robust and easy to maintain