Express.js (official website) is a web framework to simplify development of Web applications. It supports:
This lab covers how to define routes to capture HTTP requests for a web app / service endpoints. We’ll discuss how to generate simple HTTP responses. Finally, we’ll cover the usage of common middlewares.
This example shows the basic structure of an Express app.
import express from 'express';
// create an Express application, which will handle HTTP requests
var app = express();
// a route in the app, which handles GET request for the path '/'
app.get('/', (req, res) => {
res.send('hello world');
});
// more routes ...
// start listening at TCP port 3000
app.listen(3000);
When a web app receives an HTTP request, it has to decide how to handle the request, and return the result as an HTTP response. Such decision usually depends on the HTTP method (e.g. GET
, POST
, PUT
) and the path in the URL (e.g. “/about.html”). The combination of HTTP method and the URL path is usually referred to as an endpoint of the web app or web API.
In an Express app, we define a route to describe how to process HTTP requests at an endpoint.
The general syntax is app.HTTP_method(path, callback)
, where HTTP_method
stands for GET
, POST
, PUT
, PATCH
, DELETE
, etc.
The callback is sometimes called route handler, and can take 2 or more parameters. Often, we only use the first two parameters called req
and res
: req
refers to the incoming HTTP request, and res
is an object that is used to build the HTTP response. The following is a route for GET request at URL ‘/about.html’. The route returns a text response.
// app is an Express app
app.get('/about.html', (req, res) => {
// When the Express app receives a GET request
// for the path '/about.html', it returns a text response
res.send('This is a simple example of route')
});
Some web apps include parameters in the URL path. You can easily extract these parameters in Express routes. Consider the sample code below, which shows a web app to check lecture hours. When the app receives a GET request for the path ‘/lecture/comp312’, it returns a response of its lecture hours. The parameters are available in the object req.params
.
Check the source code of app1.mjs for more detail.
// retrieve the lecture time
app.get('/lecture/:code', (req, res) => {
// in a real app, we'd query a database ...
if (req.params.code=='comp312') {
res.send('Tue, Thu: 10:00-11:30am')
} else if (req.params.code=='comp311') {
// ...
}
})
The above only describes the basics of routing in Express. Refer to the online guide for more possibilities.
You can use the res
object in the route callback to build a response. (online reference) The res
object has several common methods:
to set status code and headers
res.status(code)
sets the HTTP response coderes.type(type)
sets the Content-Type of the responseres.append(field, [value])
appends a header with the given valuesres.cookie(name, value)
sets a cookieto send content in response body
res.send(body)
sends the response body. If body
is a string, text/html
is assumed. If body
is a JavaScript object, the method converts it to a JSON string (JSON.stringify()
) and use the Content type application/json
.res.json(body)
returns the data as JSON payloadres.sendFile(path)
returns a file as response. The method determines a suitable MIME type based on file type.res.redirect([status], path)
redirects to the given URLres.end()
usually used to send the response with an empty bodySome of these methods can be chained.
// retrieve the lecture time
app.get('/no-such-resource', (req, res) => {
res.status(404).end()
})
Check the source code of app2.mjs for demonstration of how to build responses with these methods.
The order of routes is significant! An Express app checks the routes in the order they are defined. When a route matches the HTTP methods and URL path, the Express app executes its callback function to handle the request. The callback usually returns an HTTP response with res.send()
or similar methods. When a response is returned, the Express app will stop checking the remaining routes. Once a response body is sent, it is said to have stopped the request-response cycle in Express. Express will not search the remaining routes (or middlewares) for the current request, and the processing of the request is completed.
app.get('/lecture/comp312', (req, res) => {
// special response for comp312
res.send('...');
});
// retrieve the lecture time
app.get('/lecture/:code', (req, res) => {
// search database, return a response
res.send('...');
});
app.get('/lecture/comp113', (req, res) => {
// special response for comp113. But sorry, this route never runs!
res.send('...');
});
Express decodes the query string of the request URL and deserializes it into a JavaScript object at req.query
.
// handle requests like GET /add?a=3&b=5
app.get('/add', (req, res) => {
let a = parseFloat(req.query.a);
let b = parseFloat(req.query.b);
res.json({
method: 'GET',
a: a, b: b, sum: a+b
});
});
To handle URL-encoded payload in request body, e.g. in POST
requests, you need to let a middleware to do the decoding before the route to process the data. The middleware express.urlencoded
deserializes the URL-encoded payload into a JavaScript object at req.body
. (Similarly, there is a built-in middleware express.json
to deserialize JSON payload.)
app.use(express.urlencoded({extended: true}));
app.post('/add', (req, res) => {
let a = parseFloat(req.body.a);
let b = parseFloat(req.body.b);
res.json({
method: 'POST',
a: a, b: b, sum: a+b
});
});
Check the source code of app3.mjs for demonstration of how to build responses with these methods.
In Express.js, both middlewares and route handlers are callback functions. When you set up an Express app, you register middlewares with app.use()
and route handlers with app.get()
(and also, for other HTTP method, e.g. app.post()
) in sequential order to build a middleware stack.
Refer to the figures in the article Express Middlewares, Demystified and Understanding Express Middleware.
When an Express app server receives an HTTP request, it will pass the request to each callback in the middleware stack in order. When a callback calls next()
, control will be transferred to the next in the stack. On the other hand, if a callback returns an HTTP response to the client (e.g. by calling res.json()
), it will stop the request-response cycle.
During the request-response cycle, a middleware function can make changes to the req
or res
object, send a response body to complete the processing of the current request, or pass the control to the next callback in the middleware stack. For more information, please refer to the official guide on writing middlewares and using middlewares.
app.use( (req, res, next) => {
// after logging this request, pass to next
console.log(`${req.method} - ${req.baseUrl}`);
next();
});
app.use( (req, res, next) => {
// has this user logged in?
if (!sessionActive) {
res.redirect('/login.html'); return;
}
next();
});
// other routes and middlewares
A useful built-in middleware is express.static
. This searches a given directory to find files that match the path in the URL being request. If found, the middleware returns the file as a response. Otherwise, it pass control to the next callback in the middleware stack. express.static
is effectively a mini web server.
// most routes and middlewares should be placed before the static middleware
app.use(express.static(__dirname + '/public'));
// if no file matches the request URL, the following routes will be checked.
app.get('special_page.html', (req,res) => { });
If a file with the same name as a route (e.g. special_page.html
) is added to the /public
folder, the express.static
middleware will stop the request-response cycle, and that route will no longer be used. Therefore, we usually put the static
middleware near the bottom of the middleware stack.
Check the source code of app4.mjs for demonstration of how to build responses with these methods.