Notes dump for Express
đź“– tl;dr: Let's learn ExpressJS
- To serve static files such as images, CSS files, and JavaScript files, use the
express.static
built-in middleware function in Express.app.use(express.static('public'))
- Express looks up the files relative to the static directory, so the name of the static directory is not part of the URL. Say for above:
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html
- The path that you provide to the express.static function is relative to the directory from where you launch your node process.
- Synchronous functions and methods tie up the executing process until they return. A single call to a synchronous function might return in a few microseconds or milliseconds, however in high-traffic websites, these calls add up and reduce the performance of the app. Avoid their use in production.
- Although Node and many modules provide synchronous and asynchronous versions of their functions, always use the asynchronous version in production. The only time when a synchronous function can be justified is upon initial startup.
console.log()
orconsole.err()
are synchronous when the destination is a terminal or a file hence not very usable in production.- If you’re logging app activity (for example, tracking traffic or API calls), instead of using console.log(), use a logging library like Winston or Bunyan.
- Node uses an
error-first callback
convention for returning errors from asynchronous functions, where the first parameter to the callback function is the error object, followed by result data in succeeding parameters. - Adding an event listener for uncaughtException will change the default behavior of the process that is encountering an exception; the process will continue to run despite the exception.
- Continuing to run the app after an uncaught exception is a dangerous practice and is not recommended
- This is why we recommend things like multiple processes and supervisors: crashing and restarting is often the most reliable way to recover from an error.
- The wrap() function is a wrapper that catches rejected promises and calls next() with the error as the first argument.
const wrap = fn => (...args) => fn(...args).catch(args[2]) app.get('/', wrap(async (req, res, next) => { const company = await getCompanyById(req.query.id) const stream = getLogoStreamById(company.id) stream.on('error', next).pipe(res) }))
- The routing methods can have more than one callback function as arguments. With multiple callback functions, it is important to provide
next
as an argument to the callback function and thencall next()
within the body of the function to hand off control to the next callback. - Query strings are not part of the route path.
- To define routes with route parameters, simply specify the route parameters in the path of the route as shown below.
app.get('/users/:userId/books/:bookId', (req, res) => { res.send(req.params) })
- More than one callback function can handle a route (make sure you specify the next object). For example:
app.get('/example/b', (req, res, next) => { console.log('the response will be sent by the next function ...') next() }, (req, res) => { res.send('Hello from B!') })
- If you need your middleware to be configurable, export a function which accepts an options object or other parameters, which, then returns the middleware implementation based on the input parameters.
module.exports = function (options) { return function (req, res, next) { // Implement the middleware function based on the options object next() } } const mw = require('./my-middleware.js') app.use(mw({ option1: '1', option2: '2' }))
- To skip the rest of the middleware functions from a router middleware stack, call
next('route')
to pass control to the next route. - Router-level middleware works in the same way as application-level middleware, except it is bound to an instance of
express.Router()
. - Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the
next
object, you must specify it to maintain the signature. - For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them. For example:
app.get('/', (req, res, next) => { fs.readFile('/file-does-not-exist', (err, data) => { if (err) { next(err) // Pass errors to Express. } else { res.send(data) } }) })
- If you pass anything to the
next()
function (except the string'route'
), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions. - When an error is written, the following information is added to the response:
- The
res.statusCode
is set fromerr.status
(orerr.statusCode
). If this value is outside the4xx
or5xx
range, it will be set to500
. - The
res.statusMessag
e is set according to the status code. - The body will be the HTML of the status code message when in production environment, otherwise will be
err.stack
. - Any headers specified in an
err.headers
object.
- The
- Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will
hang
and will not be eligible for garbage collection.function clientErrorHandler (err, req, res, next) { if (req.xhr) { res.status(500).send({ error: 'Something failed!' }) } else { next(err) } }
- A
Systemd
service file can be used to restart the node server everytime it crashes:# /etc/systemd/system/nodeserver.service [Unit] Description=Node.js Example Server #Requires=After=mysql.service # Requires the mysql service to run first [Service] ExecStart=/usr/local/bin/node /opt/nodeserver/server.js # Required on some systems #WorkingDirectory=/opt/nodeserver Restart=always # Restart service after 10 seconds if node service crashes RestartSec=10 # Output to syslog StandardOutput=syslog StandardError=syslog SyslogIdentifier=nodejs-example #User=<alternate user> #Group=<alternate group> Environment=NODE_ENV=production PORT=1337 [Install] WantedBy=multi-user.target
- The following illustrates programmatically what
Node
does on your behalf when an uncaught exception occurs:
You can override this behavior by adding anprocess.on('uncaughtException', function (er) { console.error(er.stack) process.exit(1) })
uncaughtException
handler on theprocess
object. - An
uncaughtException handler
should be treated as a last opportunity to say your goodbyes before calling process.exit. It is not advised to keep the process running.