Express Handbook
Express Handbook
Preface
The Express Handbook
Conclusion
1
Preface
The Express Handbook follows the 80/20 rule: learn in 20% of the time the
80% of a topic.
Enjoy!
2
The Express Handbook
1. Introduction to Express
2. Installation
3. The first "Hello, World" example
4. Request parameters
5. Send a response to the client
5.1 Use end() to send an empty response
5.2 Set the HTTP response status
6. Send a JSON response
7. Manage cookies
8. Work with HTTP headers
8.1 Access HTTP headers values from a request
8.2 Change any HTTP header value for a response
9. Handling redirects
10. Routing
10.1 Named parameters
10.2 Use a regular expression to match a path
11. Templates
12. Middleware
13. Serving Static Assets with Express
14. Send files to the client
15. Sessions
16. Validating input
17. Sanitizing input
18. Handling forms
19. Handling file uploads in forms
1. Introduction to Express
Express is a Web Framework built upon Node.js.
3
Express builds on top of its features to provide easy to use functionality that
satisfies the needs of the Web Server use-case. It's Open Source, free, easy to
extend and very performant.
There are also lots and lots of pre-built packages you can just drop in and use
to do all kinds of things.
2. Installation
You can install Express into any project with npm.
npm init -y
then run
Save this to an index.js file in your project root folder, and start the server
using
4
node index.js
You can open the browser to port 3000 on localhost and you should see the
Hello World! message.
Once we have the application object, we tell it to listen for GET requests on
the / path, using the get() method.
Express sends us two objects in this callback, which we called req and
res , they represent the Request and the Response objects.
Both are standards and you can read more on them here:
https://developer.mozilla.org/en-US/docs/Web/API/Request
https://developer.mozilla.org/en-US/docs/Web/API/Response
5
Request is the HTTP request. It gives us all the request information,
including the request parameters, the headers, the body of the request, and
more.
Response is the HTTP response object that we'll send to the client.
What we do in this callback is to send the 'Hello World!' string to the client,
using the Response.send() method.
This method sets that string as the body, and it closes the connection.
The last line of the example actually starts the server, and tells it to listen on
port 3000 . We pass in a callback that is called when the server is ready to
accept new requests.
4. Request parameters
I mentioned how the Request object holds all the HTTP request information.
6
Property Description
.app holds a reference to the Express app object
.baseUrl the base path on which the app responds
contains the data submitted in the request body (must
.body be parsed and populated manually before you can
access it)
contains the cookies sent by the request (needs the
.cookies
cookie-parser middleware)
7
if you pass in an object or an array, it sets the application/json Content-
res.end()
res.status(404).end()
or
sendStatus() is a shortcut:
8
res.sendStatus(200)
// === res.status(200).send('OK')
res.sendStatus(403)
// === res.status(403).send('Forbidden')
res.sendStatus(404)
// === res.status(404).send('Not Found')
res.sendStatus(500)
// === res.status(500).send('Internal Server Error')
Example:
You can send JSON to the client by using Response.json() , a useful method.
7. Manage cookies
Use the Response.cookie() method to manipulate your cookies.
Examples:
9
res.cookie('username', 'Flavio')
Value Description
domain The cookie domain name
Set the cookie expiration date. If missing, or 0, the cookie is
expires
a session cookie
Set the cookie to be accessible only by the web server. See
httpOnly
HttpOnly
Set the expiry time relative to the current time, expressed in
maxAge
milliseconds
path The cookie path. Defaults to '/'
secure Marks the cookie HTTPS only
signed Set the cookie to be signed
sameSite Value of SameSite
res.clearCookie('username')
10
app.get('/', (req, res) => {
console.log(req.headers)
})
res.set('Content-Type', 'text/html')
res.type('.html')
// => 'text/html'
res.type('html')
// => 'text/html'
res.type('json')
// => 'application/json'
res.type('application/json')
// => 'application/json'
res.type('png')
// => image/png:
9. Handling redirects
11
Redirects are common in Web Development. You can create a redirect using
the Response.redirect() method:
res.redirect('/go-there')
res.redirect(301, '/go-there')
res.redirect('../go-there')
res.redirect('..')
You can also redirect back to the Referer HTTP header value (defaulting to
/ if not set) using
res.redirect('back')
10. Routing
Routing is the process of determining what should happen when a URL is
called, or also which parts of the application should handle a specific
incoming request.
12
This creates a route that maps accessing the root domain URL / using the
HTTP GET method to the response we want to provide.
You can use multiple named parameters in the same URL, and they will all be
stored in req.params .
11. Templates
Express is capable of handling server-side template engines.
Express uses Jade as the default. Jade is the old version of Pug, specifically
Pug 1.0.
13
The name was changed from Jade to Pug due to a trademark issue in
2016, when the project released version 2. You can still use Jade, aka Pug
1.0, but going forward, it's best to use Pug 2.0
Although the last version of Jade is 3 years old (at the time of writing,
summer 2018), it's still the default in Express for backward compatibility
reasons.
In any new project, you should use Pug or another engine of your choice. The
official site of Pug is https://pugjs.org/.
You can use many different template engines, including Pug, Handlebars,
Mustache, EJS and more.
This template will create a p tag with the content Hello from Flavio .
14
You can interpolate a variable using
Look at the Pug guide for more information on how to use Pug.
This online converter from HTML to Pug will be a great help: https://html-
to-pug.com/
12. Middleware
A middleware is a function that hooks into the routing process, performing
an arbitrary operation at some point in the chain (depending on what we
want it to do).
It's commonly used to edit the request or response objects, or terminate the
request before it reaches the route handler code.
You typically use pre-made middleware, in the form of npm packages. A big
list of the available ones can be found here.
15
One example is cookie-parser , which is used to parse cookies into the
req.cookies object. You can install it using npm install cookie-parser and
you use it thusly:
app.use(cookieParser())
app.listen(3000, () => console.log('Server ready'))
We can also set a middleware function to run for specific routes only (not for
all), by using it as the second parameter of the route definition:
req.locals.name = 'Flavio'
16
const express = require('express')
const app = express()
app.use(express.static('public'))
/* ... */
If you have an index.html file in public/ , that will be served if you now hit
the root domain URL ( http://localhost:3000 )
Once a user hits a route that sends a file using this method, browsers will
prompt the user for download.
17
res.download('./file.pdf', 'user-facing-filename.pdf')
This method provides a callback function which you can use to execute code
once the file has been sent:
15. Sessions
By default Express requests are sequential and no request can be linked to
each other. There is no way to know if this request comes from a client that
already performed a request previously.
Users cannot be identified unless using some kind of mechanism that makes
it possible.
and once you're done, you can instantiate it in your application with
18
const session = require('express-session')
After this is done, all the requests to the app routes are now using sessions.
secret is the only required parameter, but there are many more you can
use. It should be a randomly unique string for your application.
This object can be used to get data out of the session, and also to set data:
req.session.name = 'Flavio'
console.log(req.session.name) // 'Flavio'
This data is serialized as JSON when stored, so you are safe to use nested
objects.
Where is the session data stored? It depends on how you set up the express-
session module.
19
It can store session data in
All solutions store the session id in a cookie, and keep the data server-side.
The client will receive the session id in a cookie, and will send it along with
every HTTP request.
We'll reference that server-side to associate the session id with the data
stored locally.
Memory is the default, it requires no special setup on your part, it's the
simplest thing but it's meant only for development purposes.
The best choice is a memory cache like Redis, for which you need to setup its
own infrastructure.
20
Say you have a POST endpoint that accepts the name, email and age
parameters:
app.use(express.json())
The best way to handle validation on any kind of input coming from outside
in Express is by using the express-validator package:
You require the check and validationResult objects from the package:
call. Every check() call accepts the parameter name as argument. Then we
call validationResult() to verify there were no validation errors. If there are
any, we tell them to the client:
21
app.post('/form', [
check('name').isLength({ min: 3 }),
check('email').isEmail(),
check('age').isNumeric()
], (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() })
}
Notice I used
isLength()
isEmail()
isNumeric()
There are many more of these methods, all coming from validator.js,
including:
isAlphanumeric()
isAscii()
isBase64()
isBoolean()
isCurrency()
isDecimal()
isEmpty()
isHash()
isHexColor()
22
isIP()
isJSON()
isLatLong()
isLength()
isLowercase()
isMobilePhone()
isNumeric()
isPostalCode()
isURL()
isUppercase()
You can validate the input against a regular expression using matches() .
isAfter() , check if the entered date is after the one you pass
isBefore() , check if the entered date is before the one you pass
isISO8601()
isRFC3339()
check('name')
.isAlpha()
.isLength({ min: 10 })
23
{
"errors": [{
"location": "body",
"msg": "Invalid value",
"param": "email"
}]
}
This default error can be overridden for each check you perform, using
withMessage() :
check('name')
.isAlpha()
.withMessage('Must be only alphabetical chars')
.isLength({ min: 10 })
.withMessage('Must be at least 10 chars long')
What if you want to write your own special, custom validator? You can use
the custom validator.
In the callback function you can reject the validation either by throwing an
exception, or by returning a rejected promise:
app.post('/form', [
check('name').isLength({ min: 3 }),
check('email').custom(email => {
if (alreadyHaveEmail(email)) {
throw new Error('Email already registered')
}
}),
check('age').isNumeric()
], (req, res) => {
const name = req.body.name
const email = req.body.email
const age = req.body.age
})
24
check('email').custom(email => {
if (alreadyHaveEmail(email)) {
throw new Error('Email already registered')
}
})
can be rewritten as
check('email').custom(email => {
if (alreadyHaveEmail(email)) {
return Promise.reject('Email already registered')
}
})
There's one thing you quickly learn when you run a public-facing server:
never trust the input.
Even if you sanitize and make sure that people can't enter weird things using
client-side code, you'll still be subject to people using tools (even just the
browser devtools) to POST directly to your endpoints.
The express-validator package you already use to validate input can also
conveniently used to perform sanitization.
Say you have a POST endpoint that accepts the name, email and age
parameters:
25
const express = require('express')
const app = express()
app.use(express.json())
app.use(express.json())
app.post('/form', [
check('name').isLength({ min: 3 }),
check('email').isEmail(),
check('age').isNumeric()
], (req, res) => {
const name = req.body.name
const email = req.body.email
const age = req.body.age
})
You can add sanitization by piping the sanitization methods after the
validation ones:
app.post('/form', [
check('name').isLength({ min: 3 }).trim().escape(),
check('email').isEmail().normalizeEmail(),
check('age').isNumeric().trim().escape()
], (req, res) => {
//...
})
26
trim() trims characters (whitespace by default) at the beginning and at
the end of a string
escape() replaces < , > , & , ' , " and / with their corresponding
HTML entities
normalizeEmail() canonicalizes an email address. Accepts several
options to lowercase email addresses or subaddresses (e.g.
[email protected] )
and /
ltrim() like trim(), but only trims characters at the start of the string
rtrim() like trim(), but only trims characters at the end of the string
stripLow() remove ASCII control characters, which are normally
invisible
27
const sanitizeValue = value => {
//sanitize...
}
app.post('/form', [
check('value').customSanitizer(value => {
return sanitizeValue(value)
}),
], (req, res) => {
const value = req.body.value
})
When the user presses the submit button, the browser will automatically
make a POST request to the /submit-form URL on the same origin of the
page. The browser sends the data contained, encoded as application/x-www-
Forms can also send data using the GET method, but the vast majority of the
forms you'll build will use POST .
28
const express = require('express')
const app = express()
app.use(express.urlencoded({
extended: true
}))
Now, you need to create a POST endpoint on the /submit-form route, and
any data will be available on Request.body :
Don't forget to validate the data before using it, using express-validator .
When the user press the submit button, the browser will automatically make
a POST request to the /submit-form URL on the same origin of the page.
The browser sends the data contained, not encoded as as a normal form
application/x-www-form-urlencoded , but as multipart/form-data .
29
Server-side, handling multipart data can be tricky and error prone, so we are
going to use a utility library called formidable. Here's the GitHub repo, it
has over 4000 stars and is well-maintained.
30
Or, you can use events instead of a callback. For example, to be notified when
each file is parsed, or other events such as completion of file processing,
receiving a non-file field, or if an error occurred:
Whichever way you choose, you'll get one or more Formidable.File objects,
which give you information about the file uploaded. These are some of the
methods you can call:
The path defaults to the temporary folder and can be modified if you listen
for the fileBegin event:
31
app.post('/submit-form', (req, res) => {
new formidable.IncomingForm().parse(req)
.on('fileBegin', (name, file) => {
file.path = __dirname + '/uploads/' + file.name
})
.on('file', (name, file) => {
console.log('Uploaded file', name, file)
})
//...
})
32
Conclusion
Thanks a lot for reading this book.
33