Express.js

Installation

To set up Express, create a Node project with:

# check is node is installed
node --version

# make node project
mkdir my-node-express-app
cd my-node-express-app
npm init

# install express.js (in the node project my-node-express-app)
npm install --save express

Basic Routing

  • API endpoint: a point at which an API connects with a software program.

  • Routing: how an app responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method

  • The HTTP methods which are supported are, GET, POST, PUT, DELETE

  • Routes have the following structure:

    app.METHOD(PATH, HANDLER)

    Where:

    ​ (app is an instance of express; 'const app = express()'),

    ​ (METHOD is an HTTP REQUEST METHOD),

    ​ (PATH is a path on the server),

    ​ (HANDLER is the callback function when the route is matched)

    ​ also called Controller

  • Simple Routes for the Hello World App above:

// Respond with 'Hello World!' on the homepage:
app.get('/', function (req, res) {
  res.send('Hello World!')
})

// Respond to POST request on the root route (/), the app’s home page:
app.post('/', function (req, res) {
  res.send('Got a POST request')
})

// Respond to a PUT request to the /user route:
app.put('/user', function (req, res) {
  res.send('Got a PUT request at /user')
})

// Respond to a DELETE request to the /user route:
app.delete('/user', function (req, res) {
  res.send('Got a DELETE request at /user')
})

Serving Static Files

  • To serve static files such as images, CSS files, and JavaScript files, use the express.static built-in middleware function in Express:

    // formally,
    express.static(root, [options])
    // 'root': the root dir from which to serve static assets
    
    //  ie. to serve static files in a root dir named 'public'
    app.use(express.static('public'))

    Now, you can load the files that are in the public directory:

    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

    To use multiple static assets directories, call the express.static middleware function multiple times:

    // serve static files from 'static' and 'files' directories
    app.use(express.static('static'))
    app.use(express.static('files'))
  • If you run the express app from another directory, it’s safer to use the absolute path of the directory that you want to serve:

    // same as above, but safer since it uses absolute path instead of relative.
    const path = require('path')
    app.use('/static', express.static(path.join(__dirname, 'public')))

Routing In-Depth

  • You define routing using methods of the Express app object that correspond to HTTP methods; for example, app.get() to handle GET requests and app.post to handle POST requests.

  • You can also use app.all() to handle all HTTP methods and app.use() to specify middleware as the callback function.

Route Methods

  • A route method is derived from one of the HTTP methods, and is attached to an instance of the express class (usually 'app'). Some basic routes:

    // GET method route
    app.get('/', function (req, res) {
      res.send('GET request to the homepage')
    })
    
    // POST method route
    app.post('/', function (req, res) {
      res.send('POST request to the homepage')
    })
  • There is a special routing method, app.all(), used to load middleware functions at a path for all HTTP request methods. Ie. the following handler is executed for requests to the route “/secret” for any HTTP verb:

    app.all('/secret', function (req, res, next) {
      console.log('Accessing the secret section ...')
      next() // pass control to the next handler
    })

Route Paths

  • Route paths, in combination with a request method, define the endpoints at which requests can be made. Query strings are not part of the route path. Some route paths:

    // This route path will match requests to the root route, /
    app.get('/', function (req, res) {
      res.send('root')
    })
    
    // This route path will match requests to /about
    app.get('/about', function (req, res) {
      res.send('about')
    })
    
    // This route path will match requests to /random.text
    app.get('/random.text', function (req, res) {
      res.send('random.text')
    })

Route Parameters

  • Route parameters are named URL segments that are used to capture the values specified at their position in the URL

  • The captured values are populated in the req.params object, with the name of the route parameter specified in the path as their respective keys.

    ie.
    
    Route path:         /users/:userId/books/:bookId
    Request URL:         http://localhost:3000/users/34/books/8989
    req.params:         { "userId": "34", "bookId": "8989" }
  • 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', function (req, res) {
      res.send(req.params)
    })
    // ie. /users/123/books/456
    // req.params = { "userId": 123, "bookId": 456 }
  • Since the hyphen (-) and the dot (.) are interpreted literally, they can be used along with route parameters for useful purposes:

    using hyphen (-):
        Route path: /flights/:from-:to
        Request URL: http://localhost:3000/flights/LAX-SFO
        req.params: { "from": "LAX", "to": "SFO" }
    
    using dot (.):
        Route path: /plantae/:genus.:species
        Request URL: http://localhost:3000/plantae/Prunus.persica
        req.params: { "genus": "Prunus", "species": "persica" }

Route Handlers

  • Also called controllers, route handlers are the callback functions that behave like middleware to handle a request. Route handlers can be in the form of a function, an array of functions, or combinations of both (see here).

  • Handlers usually have 3 parameters: a response object, a request object, and a next().

Response Methods

  • The methods on the response object (res) in the following table can send a response to the client, and terminate the request-response cycle.

  • If none of these methods are called from a route handler, the client request will be left hanging

    MethodDescription

    Prompt a file to be downloaded. res.download('/report-12345.pdf')

    End the response process. res.end() res.status(404).end()

    Send a JSON response. res.json({ user: 'tobi' }) res.status(500).json({ error: 'message' })

    Redirect a request. res.redirect('http://example.com') res.redirect(301, 'http://example.com')

    Render a view template.

    Send a response of various types. (When parameter is String, the method sets the Content-Type to “text/html”) (When the parameter is an Array or Object, Express responds with the JSON representation)

    Set the response status code and send its string representation as the response body. res.sendStatus(404)

    Sets the response’s HTTP header field to a given value. res.set('Content-Type', 'text/plain')

  • app.route() - used to create chainable route handlers for a given route path. Organizes the code with the routes:

    app.route('/book')
        // GET request
      .get(function (req, res) {
        res.send('Get a random book')
      })
    
    // POST request
      .post(function (req, res) {
        res.send('Add a book')
      })
    
        // PUT request
      .put(function (req, res) {
        res.send('Update the book')
      })

express.Router (SUB ROUTING)

  • Use the express.Router class to create modular, mountable route handlers

  • A Router instance is a complete middleware and routing system; referred as a “mini-app”

  • This helps with abstraction and organizing code.

  • The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app:

    // name.js (can be done in main file app.js, but it helps organize) router file
    
    // create Router instance
    const express = require('express')
    const router = express.Router()
    
    // middleware that is specific to this router
    router.use(function timeLog (req, res, next) {
      console.log('Time: ', Date.now())
      next()
    })
    
    router
            // define the root route for mini app
            .get('/', function (req, res) {
              res.send('Hello')
            })
            // define the /name route for mini app
            .get('/name', function (req, res) {
              res.send('James Bond')
            })
    
    // export this sub route
    module.exports = router

    You would load ('mount') the sub route onto the main app much like you load a middleware: with the app.use function. But now, you pass in the route to load it onto, then pass the subroute.

    // Then in app.js, import and load the sub router module from name.js
    var birds = require('./name')
    ...
    app.use('/api', birds)
    
    // The main app will now be able to handle requests to /api/name, as well as call the timeLog middleware function that is specific to the route.

CRUD

  • Create-Read-Update-Delete operations work perfectly with REST since there's a one-to-one correspondence with the HTTP verbs and CRUD:

    [C]reate = app.post()

    [R]ead = app.get()

    [U]pdate = app.put()

    [D]elete = app.delete()

(.post and .put only differ in their intentions; both post data, but .put expects to cause an update not a creation)


Route Order

  • If you have 2 endpoints/routes with the same verb, the one higher up will be executed since Node is top-down execution.

    // both are GET on the '/' route
    // the higher up one will run (so {data: 1} will be sent)
    app.get('/' (req, res) => {
      res.send({ data: 1})
    })
    
    app.get('/' (req, res) => {
      res.send({ data: 2})
    })

Sub Routing

  • You can have branches of routes (sub routes)


Middleware

  • Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle.

  • The next function is a function in the Express router which, when invoked, executes the middleware succeeding the current middleware.

  • Middleware functions can perform the following tasks

    • Execute any code, Make changes to the req&res objects, End the request-response cycle, Call the next middleware in the stack.

If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging

  • ie. a middleware function called myLogger simply prints “hi” on GET requests:

    const express = require('express')
    const app = express()
    
    // MIDDLEWARE FUNCTION
    var requestTime = function (req, res, next) {
      // changes the request object for the entire app
      req.requestTime = Date.now()
      next()
    }
    
    // load the middleware function
    app.use(requestTime)
    
    app.get('/', function (req, res) {
      var responseText = 'Hello World!<br>'
      responseText += '<small>Requested at: ' + req.requestTime + '</small>'
      res.send(responseText)
    })
    
    app.listen(3000)

There are many useful third party middlewares you can download namely body-parser.


Template (View) Engines with Express

A template engine enables you to use static template files in your application.

At runtime, the template engine replaces variables in a template file with actual values, and transforms the template into an HTML file sent to the client. This approach makes it easier to design an HTML page.

Some popular template engines that work with Express are Pug (formerly known as Jade), Mustache, and EJS.

  • res.render('filename') - used to render (ie. upload an html file) to the client side. This function assumes a Model-View-Controller Structure (MCV) project structure, so when you call res.render('index'), it will look inside a views folder for a filename of 'index' (index.html; don't include file extension in function call). So now, you can render different HTML pages for different routes using this method.

To use EJS:

  1. install ejs: npm i ejs --save

  2. create a folder named 'views' with file you want to render (ie. nfl.ejs); the file extension must be .ejs (it's still HTML but with a little programming language in it- kinda like jsx)

  3. app.set('view engine', 'ejs')

  4. res.render('nfl')

app.set('view engine', 'ejs')

app.get('/', (req, res) => {
        res.render('nfl')
    res.end()
})

![Screen Shot 2021-12-12 at 9.24.33 PM](/Users/dev/Library/Application Support/typora-user-images/Screen Shot 2021-12-12 at 9.24.33 PM.png)


Middleware References

> body-parser ( deprecated; now use -> express.json() )

  • allows us to read the "body" of an incoming JSON object. But it's deprecated.

  • Now, we can simply use express.json():

    const express = require('express');
    const app = express();
    
    app.use(express.urlencoded()); // Parse URL-encoded bodies
    app.use(express.json());

> cors

  • CORS stands for Cross-Origin Resource Sharing. It allows us to relax the security applied to an API. This is done by bypassing the Access-Control-Allow-Origin headers, which specify which origins can access the API.

  • The three parts that form an origin are protocol, domain, and port. (http [protocol] localhost [domain] :3000 [port]). There are servers that host APIs and ensure that info is delivered to websites and other end points. Therefore, making cross-origin calls. read more here.

    npm install cors --save
    // Simple Usage (Enable All CORS Requests)
    
    var express = require('express')
    var cors = require('cors')
    var app = express()
    
    app.use(cors())
    
    app.get('/products/:id', function (req, res, next) {
      res.json({msg: 'This is CORS-enabled for all origins!'})
    })
    
    app.listen(80, function () {
      console.log('CORS-enabled web server listening on port 80')
    })
    // Enable CORS for a Single Route
    
    var express = require('express')
    var cors = require('cors')
    var app = express()
    
    app.get('/products/:id', cors(), function (req, res, next) {
      res.json({msg: 'This is CORS-enabled for a Single Route'})
    })
    
    app.listen(80, function () {
      console.log('CORS-enabled web server listening on port 80')
    })

> morgan

  • simplifies the task of logging HTTP requests and errors to and from your application.

    npm install morgan --save

    And now all you need in your app is (either 'dev' or 'tiny'):

    // import the module
    const morgan = require('morgan')
    
    // load the middleware so now it will log all requests and errors.
    // JUST USE 'dev' (EASIEST)
    app.use(morgan('dev'))

    the 'dev' parameter can be swapped with 'combined', 'common', 'short', 'tiny'- each is a different format of the log:

    'combined' - Standard Apache combined log output.
        :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
    
    'common' - Standard Apache common log output
        :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]
    
    'dev' - Concise output colored by response status for dev. use
        :method :url :status :response-time ms - :res[content-length]
    
    'short' - Shorter than default, also including response time
        :remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms
    
    'tiny' - The minimal output
        :method :url :status :res[content-length] - :response-time ms

Last updated