NodeJS Express

creator @tjholowaychuk">TJ Holowaychuk

Reference

Express v4+

  • don’t need app.use(app.router) after express v3

NPM Licenses

  • npm license-checker checks licenses of npm package dependencies.
  • some npm packages might not allow the use on closed-source software.
  • MIT license allows you to do anything
  • GNU/GPL license can NOT be used in close-sourced software
  • Dual license MIT+GPL is possible.

Browser parsing

  1. import UAParser from 'ua-parser';
  2. browserCheck (req, res, next) {
  3. const browser = UAParser.parse(req.headers['user-agent']);
  4. let isBrowserSupported = false,
  5. isMobile = false;
  6. SupportedBrowsers.forEach((f) => {
  7. if (/Mobile/.test(browser.ua.family
  8. || /Android/.test(browser.os.family))) {
  9. isMobile = true;
  10. }
  11. if (browser.ua.family === f.family && browser.ua.major >= f.major) {
  12. isBrowserSupported = true;
  13. }
  14. });
  15. if (!isBrowserSupported) {
  16. // complain about the browser
  17. }
  18. }

Request Headers

info sent by the browser to the server

  • url
  • language
  • user agent (browser, OS, hardware)

Response Headers

server also sends invisible info

  • Content-Type
  • is it compressed?
  • encoding type
  • cache expiration
  • server type and OS (hint for hackers… app.disable('x-powered-by')

Content Type

  • it is an Internet Media Type
  • MIME was a precursor type/subtype; params eg. text/html; charset=UTF-8
  • Internet Media Types

Request Body

  • most common media type for POST bodies is application/x-www-form-urlencoded which is similar to querystring
  • if POST needs file uploads it uses multipart/form-data
  • AJAX uses application/json
  • Normal GET requests lack bodies

Parameters

Can come from

  • querystring
  • session
  • request body
  • named routing parameters

Request Object

Created as an instance of http.IncomingMessage

  • req.params: [by express] array with named route parameters
  • req.param(): [by express] method to get the named route parameter
  • req.query: [by express] dictionary of querystring parameters
  • req.body: [by express] Object dictionary containing POST params from body. To make it available you need middleware
  • req.route: [by express] info about route. Useful for debugging
  • req.cookies & req.signedCookies: [by express] cookie dictionary
  • req.headers: [Node] headers from client
  • req.accepts: [express] method to determine if a client accepts certain types. eg. MIME types, arrays, comma list
  • req.ip: [express] client ip address
  • req.path: [express] path without protocol, host, port or querystring
  • req.host: [express] method that returns hostname from client. This can be spoofed. Warning: should not be used for security purposes.
  • req.xhr: [express] boolean states if request was AJAX
  • req.protocol: [express] http or https
  • req.secure: [express] boolean equivalent to req.protocol === 'https'
  • req.url / req.originalUrl: [node/ express]
  • req.acceptedLanguages: [express] array of human languages the client prefer. Parsed from req.header

Response Object

created on http.ServerResponse

  • res.status(): method to set status code (HTTP). Default is 200. FOr redirects use 301, 302, 303 & 307 (use res.redirect method instead)
  • res.set(name, value): set response header
  • res.cookie(name, value, [options]) & res.clearCookie(name, [options]): sets or clears cookies that will be stared on the client.
  • res.redirect([status], url): Redirects browser. Default code 302 (found). Avoid redirection unless permanently moving a page. 301 (Moved Permanently).
  • res.send(body) & res.send(status.body): Sends response to client. Defaults content type text/html. To change `res.set(‘Content-Type’, ‘text/plain’). If body is object or array it sends it as json
  • res.json(json): sends JSON. Use this instead of res.send if sending JSON.
  • res.jsonp(json): sends JSONP
  • res.type(type): Method equivalent to res.set('Content-Type', type) except it will attempt to map file extensions to an Internet media type eg. res.type('txt') to text/plain
  • res.format(object): Method to send different content depending on the Accept request header.
    1. app.get('/api/tours', (req, res) => {
    2. var toursXml = '<?xml version="1.0"?><tour>Tour1</tour>';
    3. var toursText = 'Tour1';
    4. res.format({
    5. 'application/json': () => res.json(tours),
    6. 'application/xml': () => {
    7. res.type('application/xml');
    8. res.send(toursXml);
    9. },
    10. 'text/xml': () => {
    11. res.type('text/xml');
    12. res.send(toursXml);
    13. },
    14. 'text/plain': () => {
    15. res.type('text/plain');
    16. res.send(toursText);
    17. }
    18. });
  • res.attachment([filename]) & res.download(path, [filename], [callback]): Both of these methods set a response header called Content-Disposition to attachment. This will prompt the browser to download the content instead of displaying it in a browser. attachment will only set the headers but you’ll have to send the conent. With download you can specify a file path to send.
  • res.sendFile(path, [options], [callback]): will read the contents of a file and send the contents.
  • res.links(links): sets links response header.
  • res.locals & res.render(view, [locals], callback): res.locals is an object with default context for rendering views. res.render will render a view using the configured templating engine.

Form Handling

  • Use redirect 303 instead of direct HTML response since it doesn’t interfere with back button & book marking
  • Use body-parser middleware for POST body
  • You can use querystring on POST by adding it to the forms action path eg. <form action="/process?form=news">
  • Use the same URL endpoint for AJAX and normal POST fallback. This hides implementation details. good: /offers bad /api/offers
  • Detect AJAX. if(req.xhr || req.accepts('json, html') === 'json') {

File Uploads

Two famous libraries for multipart processing:

  • Busboy
  • Formidable (easier to uses)

eg.

  1. <form enctype="multipart/formm-data" action="/" method="POST">
  2. <input type="file" accept="image/*" name="photo" />

and in the server

  1. var formidable = require('formidable');
  2. app.get('/contest/vacation-photo', function(req, res) {
  3. var now = new Date();
  4. res.render('contest/vacation-photo', {
  5. year: now.getFullYear(), month: now.getMonth()
  6. });
  7. });
  8. app.post('/contest/vacation-photo/:year/:month', function(req, res) {
  9. var form = new formidable.IncomingForm();
  10. form.parse(req, function(err, fields, files) {
  11. if(err) return res.redirect(303, '/error');
  12. console.log('received fields:');
  13. console.log(fields);
  14. console.log('received files');
  15. console.log(files);
  16. res.redirect(303, '/thank-you');
  17. });
  18. });

Stress Testing

to have confidence that your app will function under the load of hundreds of thousands of simultaneous requests

  1. var loadtest = require('loadtest');
  2. var expect = require('chai').expect;
  3. suite('Stress tests', function () {
  4. test('Homepage should handle 100 requests in a second', function (done) {
  5. var options = {
  6. url: 'http://localhost:3000',
  7. concurrency: 4,
  8. maxRequests: 100
  9. };
  10. loadtest.loadTest(options, function(err, result) {
  11. expect(!err);
  12. expect(result.totalTimeSeconds < 1);
  13. done();
  14. });
  15. });
  16. });

Static Resources

Expires/Cache-Control

These two headers tell your browser the max amount of time a resource can be cached.

  • You only need one of these headers
  • Expires is more broadly supported
  • Cache-Control is good for development but not good for production

Last-Modified/ETag

These two tags tell the browser to validate the tags before downloading the content.

  • Last-Modified will make the browser compare the date of last modification to see if it should download again
  • ETag will make the browser compare to a tag eg. a build number.

CDN

CDNs will handle the cache headers with good defaults.

Cache-busting

Finger printing

the act of appending a number or tag at the end of a file name.

  • If using a CDN make sure you fingerprint all the files.