You can get a PDF, ePub and Mobi version of this page for an easier reference, or to read it on your Kindle or tablet.
Node.js is an amazing tool for building networking services and applications.
Express builds on top of its features to provide easy to use functionality that satisfy the needs of the Web Server use case.
It’s Open Source, free, easy to extend, very performant, and has lots and lots of pre-built packages you can just drop in and use, to perform all kind of things.
You can install Express into any project with npm:
npm install express --save
or Yarn:
yarn add express
Both commands will also work in an empty directory, to start up your project from scratch, although npm
does not create a package.json
file at all, and Yarn creates a basic one.
Just run npm init
or yarn init
if you're starting a new project from scratch.
We’re ready to create our first Express Web Server.
Here is some code:
const express = require('express')const app = express()
app.get('/', (req, res) => res.send('Hello World!'))app.listen(3000, () => console.log('Server ready'))
Save this to an index.js
file in your project root folder, and start the server using
node index.js
You can open the browser to port 3000 on localhost and you should see the Hello World!
message.
Those 4 lines of code do a lot behind the scenes.
First, we import the express
package to the express
value.
We instantiate an application by calling its app()
method.
Once we have the application object, we tell it to listen for GET requests on the /
path, using the get()
method.
There is a method for every HTTP verb: get()
, post()
, put()
, delete()
, patch()
:
app.get('/', (req, res) => { /* */ })app.post('/', (req, res) => { /* */ })app.put('/', (req, res) => { /* */ })app.delete('/', (req, res) => { /* */ })app.patch('/', (req, res) => { /* */ })
Those methods accept a callback function, which is called when a request is started, and we need to handle it.
We pass in an arrow function:
(req, res) => res.send('Hello World!')
Express sends us two objects in this callback, which we called req
and res
, that represent the Request and the Response objects.
Request is the HTTP request. It can give us all the info about that, 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.
I mentioned how the Request object holds all the HTTP request information.
These are the main properties you’ll likely use:
cookie-parser
middleware)cookie-parser
middleware)The query string is the part that comes after the URL path and starts with an exclamation mark ?
.
Example:
?name=flavio
Multiple query parameters can be added using &
:
?name=flavio&age=35
How do you get those query string values in Express?
Express makes it very easy by populating the Request.query
object for us:
const express = require('express')const app = express()
app.get('/', (req, res) => { console.log(req.query)})
app.listen(8080)
This object is filled with a property for each query parameter.
If there are no query params, it’s an empty object.
This makes it easy to iterate on it using the for…in loop:
for (const key in req.query) { console.log(key, req.query[key])}
This will print the query property key and the value.
You can access single properties as well:
req.query.name //flavioreq.query.age //35
POST query parameters are sent by HTTP clients for example by forms, or when performing a POST request sending data.
How can you access this data?
If the data was sent as JSON, using Content-Type: application/json
, you will use the express.json()
middleware:
const express = require('express')const app = express()
app.use(express.json())
If the data was sent as JSON, using Content-Type: application/x-www-form-urlencoded
, you will use the express.urlencoded()
middleware:
const express = require('express')const app = express()
app.use(express.urlencoded())
In both cases you can access the data by referencing it from Request.body
:
app.post('/form', (req, res) => { const name = req.body.name})
Note: older Express versions required the use of the
_body-parser_
module to process POST data. This is no longer the case as of Express 4.16 (released in September 2017) and later versions.
In the Hello World example we used the Response.send()
method to send a simple string as a response, and to close the connection:
(req, res) => res.send('Hello World!')
If you pass in a string, it sets the Content-Type
header to text/html
.
if you pass in an object or an array, it sets the application/json
Content-Type
header, and parses that parameter into JSON.
send()
automatically sets the Content-Length
HTTP response header.
send()
also automatically closes the connection.
An alternative way to send the response, without any body, it’s by using the Response.end()
method:
res.end()
Use the Response.status()
:
res.status(404).end()
or
res.status(404).send('File not found')
sendStatus()
is a shortcut:
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')
When you listen for connections on a route in Express, the callback function will be invoked on every network call with a Request object instance and a Response object instance.
Example:
app.get('/', (req, res) => res.send('Hello World!'))
Here we used the Response.send()
method, which accepts any string.
You can send JSON to the client by using Response.json()
, a useful method.
It accepts an object or array, and converts it to JSON before sending it:
res.json({ username: 'Flavio' })
Use the Response.cookie()
method to manipulate your cookies.
Examples:
res.cookie('username', 'Flavio')
This method accepts a third parameter which contains various options:
res.cookie('username', 'Flavio', { domain: '.flaviocopes.com', path: '/administrator', secure: true })
res.cookie('username', 'Flavio', { expires: new Date(Date.now() + 900000), httpOnly: true })
The most useful parameters you can set are:
domain
the cookie domain name
expires
set the cookie expiration date. If missing, or 0, the cookie is a session cookie
httpOnly
set the cookie to be accessible only by the web server. See HttpOnly
maxAge
set the expiry time relative to the current time, expressed in milliseconds
path
the cookie path. Defaults to /
secure
Marks the cookie HTTPS only
signed
set the cookie to be signed
sameSite
Value of [SameSite](https://flaviocopes.com/cookies/#samesite)
A cookie can be cleared with
res.clearCookie('username')
You can access all the HTTP headers using the Request.headers
property:
app.get('/', (req, res) => { console.log(req.headers)})
Use the Request.header()
method to access one individual request header value:
app.get('/', (req, res) => { req.header('User-Agent')})
You can change any HTTP header value using Response.set()
:
res.set('Content-Type', 'text/html')
There is a shortcut for the Content-Type header however:
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:
Redirects are common in Web Development. You can create a redirect using the Response.redirect()
method:
res.redirect('/go-there')
This creates a 302 redirect.
A 301 redirect is made in this way:
res.redirect(301, '/go-there')
You can specify an absolute path (/go-there
), an absolute url (https://anothersite.com
), a relative path (go-there
) or use the ..
to go back one level:
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')
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.
In the Hello World example, we used this code
app.get('/', (req, res) => { /* */ })
This creates a route that maps accessing the root domain URL /
using the HTTP GET method to the response we want to provide.
What if we want to listen for custom requests, maybe we want to create a service that accepts a string, and returns that uppercase, and we don’t want the parameter to be sent as a query string, but part of the URL. We use named parameters:
app.get('/uppercase/:theValue', (req, res) => res.send(req.params.theValue.toUpperCase()))
If we send a request to /uppercase/test
, we'll get TEST
in the body of the response.
You can use multiple named parameters in the same URL, and they will all be stored in req.params
.
You can use regular expressions to match multiple paths with one statement:
app.get(/post/, (req, res) => { /* */ })
will match /post
, /post/first
, /thepost
, /posting/something
, and so on.
A JavaScript application running in the browser can usually only access HTTP resources on the same domain (origin) that serves it.
Loading images or scripts/styles always works, but XHR and Fetch calls to another server will fail, unless that server implements a way to allow that connection.
This way is called CORS, Cross-Origin Resource Sharing.
Also loading Web Fonts using @font-face
has same-origin policy by default, and other less popular things (like WebGL textures and drawImage
resources loaded in the Canvas API).
One very important thing that needs CORS is ES Modules, recently introduced in modern browsers.
If you don’t set up a CORS policy on the server that allows to serve 3rd part origins, the request will fail.
Fetch example:
XHR example:
A Cross-Origin resource fails if it’s:
and it’s there for your security, to prevent malicious users to exploit the Web Platform.
But if you control both the server and the client, you have all the good reasons to allow them to talk to each other.
How?
It depends on your server-side stack.
Pretty good (basically all except IE<10):
If you are using Node.js and Express as a framework, use the CORS middleware package.
Here’s a simple implementation of an Express Node.js server:
const express = require('express')const app = express()
app.get('/without-cors', (req, res, next) => { res.json({ msg: '😞 no CORS, no party!' })})
const server = app.listen(3000, () => { console.log('Listening on port %s', server.address().port)})
If you hit /without-cors
with a fetch request from a different origin, it's going to raise the CORS issue.
All you need to do to make things work out is to require the cors
package linked above, and pass it in as a middleware function to an endpoint request handler:
const express = require('express')const cors = require('cors')const app = express()
app.get('/with-cors', cors(), (req, res, next) => { res.json({ msg: 'WHOAH with CORS it works! 🔝 🎉' })})
/* the rest of the app */
I made a simple Glitch example. Here is the client working, and here’s its code: https://glitch.com/edit/#!/flavio-cors-client.
This is the Node.js Express server: https://glitch.com/edit/#!/flaviocopes-cors-example-express
Note how the request that fails because it does not handle the CORS headings correctly is still received, as you can see in the Network panel, where you find the message the server sent:
This example has a problem however: ANY request will be accepted by the server as cross-origin.
As you can see in the Network panel, the request that passed has a response header access-control-allow-origin: *
:
You need to configure the server to only allow one origin to serve, and block all the others.
Using the same cors
Node library, here's how you would do it:
const cors = require('cors')
const corsOptions = { origin: 'https://yourdomain.com'}
app.get('/products/:id', cors(corsOptions), (req, res, next) => { //...})
You can serve more as well:
const whitelist = ['http://example1.com', 'http://example2.com']const corsOptions = { origin: function(origin, callback) { if (whitelist.indexOf(origin) !== -1) { callback(null, true) } else { callback(new Error('Not allowed by CORS')) } }}
There are some requests that are handled in a “simple” way. All GET
requests belong to this group.
Also some POST
and HEAD
requests do as well.
POST
requests are also in this group if they satisfy the requirement of using a Content-Type of
application/x-www-form-urlencoded
multipart/form-data
text/plain
All other requests must run through a pre-approval phase, called preflight. The browser does this to determine if it has the permission to perform an action, by issuing an OPTIONS
request.
A preflight request contains a few headers that the server will use to check permissions (irrelevant fields omitted):
OPTIONS /the/resource/you/requestAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: origin, x-requested-with, acceptOrigin: https://your-origin.com
The server will respond with something like this(irrelevant fields omitted):
HTTP/1.1 200 OKAccess-Control-Allow-Origin: https://your-origin.comAccess-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
We checked for POST, but the server tells us we can also issue other HTTP request types for that particular resource.
Following the Node.js Express example above, the server must also handle the OPTIONS request:
var express = require('express')var cors = require('cors')var app = express()
//allow OPTIONS on just one resourceapp.options('/the/resource/you/request', cors())
//allow OPTIONS on all resourcesapp.options('*', cors())
Express is capable of handling server-side template engines.
Template engines allow us to add data to a view, and generate HTML dynamically.
Express uses Jade as the default. Jade is the old version of Pug, specifically Pug 1.0.
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.
To use Pug we must first install it:
npm install pug
and when initializing the Express app, we need to set it:
const express = require('express')const app = express()app.set('view engine', 'pug')
We can now start writing our templates in .pug
files.
Create an about view:
app.get('/about', (req, res) => { res.render('about')})
and the template in views/about.pug
:
p Hello from Flavio
This template will create a p
tag with the content Hello from Flavio
.
You can interpolate a variable using
app.get('/about', (req, res) => { res.render('about', { name: 'Flavio' })})
p Hello from #{name}
This is a very short introduction to Pug, in the context of using it with Express. Look at the Pug guide for more information on how to use Pug.
If you are used to template engines that use HTML and interpolate variables, like Handlebars (described next), you might run into issues, especially when you need to convert existing HTML to Pug. This online converter from HTML to Jade (which is very similar, but a little different than Pug) will be a great help: https://jsonformatter.org/html-to-jade
Let’s try and use Handlebars instead of Pug.
You can install it using npm install hbs
.
Put an about.hbs
template file in the views/
folder:
Hello from {{name}}
and then use this Express configuration to serve it on /about
:
const express = require('express')const app = express()const hbs = require('hbs')
app.set('view engine', 'hbs')app.set('views', path.join(__dirname, 'views'))
app.get('/about', (req, res) => { res.render('about', { name: 'Flavio' })})
app.listen(3000, () => console.log('Server ready'))
You can also render a React application server-side, using the [express-react-views](https://github.com/reactjs/express-react-views)
package.
Start with npm install express-react-views react react-dom
.
Now instead of requiring hbs
we require express-react-views
and use that as the engine, using jsx
files:
const express = require('express')const app = express()
app.set('view engine', 'jsx')app.engine('jsx', require('express-react-views').createEngine())
app.get('/about', (req, res) => { res.render('about', { name: 'Flavio' })})
app.listen(3000, () => console.log('Server ready'))
Just put an about.jsx
file in views/
, and calling /about
should present you an "Hello from Flavio" string:
const React = require('react')
class HelloMessage extends React.Component { render() { return <div>Hello from {this.props.name}</div> }}
module.exports = HelloMessage
What is Pug? It’s a template engine for server-side Node.js applications.
Express is capable of handling server-side template engines. Template engines allow us to add data to a view, and generate HTML dynamically.
Pug is a new name for an old thing. It’s Jade 2.0.
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
Also see the differences between Jade and Pug
Express uses Jade as the default. Jade is the old version of Pug, specifically Pug 1.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/.
p Hello from Flavio
This template will create a p
tag with the content Hello from Flavio
.
As you can see, Pug is quite special. It takes the tag name as the first thing in a line, and the rest is the content that goes inside it.
If you are used to template engines that use HTML and interpolate variables, like Handlebars (described next), you might run into issues, especially when you need to convert existing HTML to Pug. This online converter from HTML to Jade (which is very similar, but a little different than Pug) will be a great help: https://jsonformatter.org/html-to-jade
Installing Pug is as simple as running npm install
:
npm install pug
and when initializing the Express app, we need to set it:
const express = require('express')const app = express()app.set('view engine', 'pug')app.set('views', path.join(__dirname, 'views'))
Create an about view:
app.get('/about', (req, res) => { res.render('about')})
and the template in views/about.pug
:
p Hello from Flavio
This template will create a p
tag with the content Hello from Flavio
.
You can interpolate a variable using
app.get('/about', (req, res) => { res.render('about', { name: 'Flavio' })})
p Hello from #{name}
You can interpolate a function return value using
app.get('/about', (req, res) => { res.render('about', { getName: () => 'Flavio' })})
p Hello from #{getName()}
p#titlep.title
doctype html
html head meta(charset='utf-8') meta(http-equiv='X-UA-Compatible', content='IE=edge') meta(name='description', content='Some description') meta(name='viewport', content='width=device-width, initial-scale=1')
html head script(src="script.js") script(src='//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')
link(rel='stylesheet', href='css/main.css')
script alert('test')
script (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; e=o.createElement(i);r=o.getElementsByTagName(i)[0]; e.src='//www.google-analytics.com/analytics.js'; r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); ga('create','UA-XXXXX-X');ga('send','pageview');
ul each color in ['Red', 'Yellow', 'Blue'] li= color
ul each color, index in ['Red', 'Yellow', 'Blue'] li= 'Color number ' + index + ': ' + color
if name h2 Hello from #{name}else h2 Hello
else-if works too:
if name h2 Hello from #{name}else if anotherName h2 Hello from #{anotherName}else h2 Hello
You can set variables in Pug templates:
- var name = 'Flavio'- var age = 35- var roger = { name: 'Roger' }- var dogs = ['Roger', 'Syd']
You can increment a numeric variable using ++
:
age++
p= name
span.age= age
You can use for
or each
. There is no difference.
for dog in dogs li= dog
ul each dog in dogs li= dog
You can use .length
to get the number of items:
p There are #{values.length}
while
is another kind of loop:
- var n = 0;
ul while n <= 5 li= n++
In a Pug file you can include other Pug files:
include otherfile.pug
A well organized template system will define a base template, and then all the other templates extend from it.
The way a part of a template can be extended is by using blocks:
html head script(src="script.js") script(src='//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')
link(rel='stylesheet', href='css/main.css') block head body block body h1 Home page p welcome
In this case one block, body
, has some content, while head
does not. head
is intended to be used to add additional content to the heading, while the body
content is made to be overridden by other pages.
A template can extend a base template by using the extends
keyword:
extends home.pug
Once this is done, you need to redefine blocks. All the content of the template must go into blocks, otherwise the engine does not know where to put them.
Example:
extends home.pug
block body h1 Another page p Hey! ul li Something li Something else
You can redefine one or more blocks. The ones not redefined will be kept with the original template content.
Comments in Pug can be of two types: visible or not visible in the resulting HTML.
Inline:
// some comment
Block:
// some comment
Inline:
//- some comment
Block:
//- some comment
A middleware is a function that hooks into the routing process, and performs some operation at some point, depending on what it want to do.
It’s commonly used to edit the request or response objects, or terminate the request before it reaches the route handler code.
It’s added to the execution stack like this:
app.use((req, res, next) => { /* */ })
This is similar to defining a route, but in addition to the Request and Response objects instances, we also have a reference to the next middleware function, which we assign to the variable next
.
We always call next()
at the end of our middleware function, to pass the execution to the next handler, unless we want to prematurely end the response, and send it back to the client.
You typically use pre-made middleware, in the form of npm
packages. A big list of the available ones is here.
One example is cookie-parser
, which is used to parse the cookies into the req.cookies
object. You install it using npm install cookie-parser
and you can use it like this:
const express = require('express')const app = express()const cookieParser = require('cookie-parser')
app.get('/', (req, res) => res.send('Hello World!'))
app.use(cookieParser())app.listen(3000, () => console.log('Server ready'))
You 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:
const myMiddleware = (req, res, next) => { /* ... */ next()}
app.get('/', myMiddleware, (req, res) => res.send('Hello World!'))
If you need to store data that’s generated in a middleware to pass it down to subsequent middleware functions, or to the request handler, you can use the Request.locals
object. It will attach that data to the current request:
req.locals.name = 'Flavio'
It’s common to have images, CSS and more in a public
subfolder, and expose them to the root level:
const express = require('express')const app = express()
app.use(express.static('public'))
/* ... */
app.listen(3000, () => console.log('Server ready'))
If you have an index.html
file in public/
, that will be served if you now hit the root domain URL (http://localhost:3000
)
Express provides a handy method to transfer a file as attachment: Response.download()
.
Once a user hits a route that sends a file using this method, browsers will prompt the user for download.
The Response.download()
method allows you to send a file attached to the request, and the browser instead of showing it in the page, it will save it to disk.
app.get('/', (req, res) => res.download('./file.pdf'))
In the context of an app:
const express = require('express')const app = express()
app.get('/', (req, res) => res.download('./file.pdf'))app.listen(3000, () => console.log('Server ready'))
You can set the file to be sent with a custom filename:
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:
res.download('./file.pdf', 'user-facing-filename.pdf', (err) => { if (err) { //handle error return } else { //do something }})
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.
That’s what sessions are.
When implemented, every user of you API or website will be assigned a unique session, and this allows you to store the user state.
We’ll use the express-session
module, which is maintained by the Express team.
You can install it using
npm install express-session
and once you’re done, you can instantiate it in your application with
const session = require('express-session')
This is a middleware, so you install it in Express using
const express = require('express')const session = require('express-session')
const app = express()app.use(session( 'secret': '343ji43j4n3jn4jk3n'))
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 you application.
The session is attached to the request, so you can access it using req.session
here:
app.get('/', (req, res, next) => { // req.session}
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.
You can use sessions to communicate data to middleware that’s executed later, or to retrieve it later on on subsequent requests.
Where is the session data stored? it depends on how you set up the express-session
module.
It can store session data in
There is a big list of 3rd packages that implement a wide variety of different compatible caching stores in https://github.com/expressjs/session
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.
Another popular package to manage sessions in Express is cookie-session
, which has a big difference: it stores data client-side in the cookie. I do not recommend doing that because storing data in cookies means that it's stored client-side, and sent back and forth in every single request made by the user. It's also limited in size, as it can only store 4 kilobytes of data. Cookies also need to be secured, but by default they are not, since secure Cookies are possible on HTTPS sites and you need to configure them if you have proxies.
Say you have a POST endpoint that accepts the name, email and age parameters:
const express = require('express')const app = express()
app.use(express.json())
app.post('/form', (req, res) => { const name = req.body.name const email = req.body.email const age = req.body.age})
How do you server-side validate those results to make sure
The best way to handle validating any kind of input coming from outside in Express is by using the [express-validator](https://express-validator.github.io/)
package:
npm install express-validator
You require the check
object from the package:
const { check } = require('express-validator/check')
We pass an array of check()
calls as the second argument of the post()
call. Every check()
call accepts the parameter name as argument:
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})
Notice I used
isLength()
isEmail()
isNumeric()
There are many more of these methods, all coming from validator.js, including:
contains()
, check if value contains the specified valueequals()
, check if value equals the specified valueisAlpha()
isAlphanumeric()
isAscii()
isBase64()
isBoolean()
isCurrency()
isDecimal()
isEmpty()
isFQDN()
, is a fully qualified domain name?isFloat()
isHash()
isHexColor()
isIP()
isIn()
, check if the value is in an array of allowed valuesisInt()
isJSON()
isLatLong()
isLength()
isLowercase()
isMobilePhone()
isNumeric()
isPostalCode()
isURL()
isUppercase()
isWhitelisted()
, checks the input against a whitelist of allowed charactersYou can validate the input against a regular expression using matches()
.
Dates can be checked using
isAfter()
, check if the entered date is after the one you passisBefore()
, check if the entered date is before the one you passisISO8601()
isRFC3339()
For exact details on how to use those validators, refer to https://github.com/chriso/validator.js#validators.
All those checks can be combined by piping them:
check('name') .isAlpha() .isLength({ min: 10 })
If there is any error, the server automatically sends a response to communicate the error. For example if the email is not valid, this is what will be returned:
{ "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})
The custom validator:
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') }})
You’ve seen how to validate input that comes from the outside world to your Express app.
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.
Or bots trying every possible combination of exploit known to humans.
What you need to do is sanitizing your input.
The [express-validator](https://express-validator.github.io/)
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:
const express = require('express')const app = express()
app.use(express.json())
app.post('/form', (req, res) => { const name = req.body.name const email = req.body.email const age = req.body.age})
You might validate it using:
const express = require('express')const app = express()
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) => { //...})
Here I used the methods:
trim()
trims characters (whitespace by default) at the beginning and at the end of a stringescape()
replaces <
, >
, &
, '
, "
and /
with their corresponding HTML entitiesnormalizeEmail()
canonicalizes an email address. Accepts several options to lowercase email addresses or subaddresses (e.g. [email protected]
)Other sanitization methods:
blacklist()
remove characters that appear in the blacklistwhitelist()
remove characters that do not appear in the whitelistunescape()
replaces HTML encoded entities with <
, >
, &
, '
, "
and /
ltrim()
like trim(), but only trims characters at the start of the stringrtrim()
like trim(), but only trims characters at the end of the stringstripLow()
remove ASCII control characters, which are normally invisibleForce conversion to a format:
toBoolean()
convert the input string to a boolean. Everything except for '0', 'false' and '' returns true. In strict mode only '1' and 'true' return truetoDate()
convert the input string to a date, or null if the input is not a datetoFloat()
convert the input string to a float, or NaN if the input is not a floattoInt()
convert the input string to an integer, or NaN if the input is not an integerLike with custom validators, you can create a custom sanitizer.
In the callback function you just return the sanitized value:
const sanitizeValue = value => { //sanitize...}
app.post('/form', [ check('value').customSanitizer(value => { return sanitizeValue(value) }),], (req, res) => { const value = req.body.value})
This is an example of an HTML form:
<form method="POST" action="/submit-form"> <input type="text" name="username" /> <input type="submit" /></form>
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, sending the data it contains, encoded as application/x-www-form-urlencoded
. In this case, the form data contains the username
input field value.
Forms can also send data using the GET
method, but the vast majority of the forms you'll build will use POST
.
The form data will be sent in the POST request body.
To extract it, you will use the express.urlencoded()
middleware, provided by Express:
const express = require('express')const app = express()
app.use(express.urlencoded())
Now you need to create a POST
endpoint on the /submit-form
route, and any data will be available on Request.body
:
app.post('/submit-form', (req, res) => { const username = req.body.username //... res.end()})
Don’t forget to validate the data before using it, using express-validator
.
This is an example of an HTML form that allows a user to upload a file:
<form method="POST" action="/submit-form"> <input type="file" name="document" /> <input type="submit" /></form>
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, sending the data it contains, not encoded as application/x-www-form-urlencoded
as a normal form, but as multipart/form-data
.
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 well maintained.
You can install it using:
npm install formidable
Then in your Node.js file, include it:
const express = require('express')const app = express()const formidable = require('formidable')
Now in the POST
endpoint on the /submit-form
route, we instantiate a new Formidable form using formidable.IncomingFrom()
:
app.post('/submit-form', (req, res) => { new formidable.IncomingFrom()})
After doing so, we need to parse the form. We can do so synchronously by providing a callback, which means all files are processed, and once formidable is done, it makes them available:
app.post('/submit-form', (req, res) => { new formidable.IncomingFrom().parse(req, (err, fields, files) => { if (err) { console.error('Error', err) throw err } console.log('Fields', fields) console.log('Files', files) files.map(file => { console.log(file) }) })})
Or you can use events instead of a callback, to be notified when each file is parsed, and other events, like ending processing, receiving a non-file field, or an error occurred:
app.post('/submit-form', (req, res) => { new formidable.IncomingFrom().parse(req) .on('field', (name, field) => { console.log('Field', name, field) }) .on('file', (name, file) => { console.log('Uploaded file', name, file) }) .on('aborted', () => { console.error('Request aborted by the user') }) .on('error', (err) => { console.error('Error', err) throw err }) .on('end', () => { res.end() })})
Whatever 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:
file.size
, the file size in bytesfile.path
, the path this file is written tofile.name
, the name of the filefile.type
, the MIME type of the fileThe path defaults to the temporary folder and can be modified if you listen to the fileBegin
event:
app.post('/submit-form', (req, res) => { new formidable.IncomingFrom().parse(req) .on('fileBegin', (name, file) => { form.on('fileBegin', (name, file) => { file.path = __dirname + '/uploads/' + file.name }) }) .on('file', (name, file) => { console.log('Uploaded file', name, file) }) //...})
To be able to serve a site on HTTPS from localhost you need to create a self-signed certificate.
A self-signed certificate will be enough to establish a secure HTTPS connection, although browsers will complain that the certificate is self-signed and as such it’s not trusted. It’s great for development purposes.
To create the certificate you must have OpenSSL installed on your system.
You might have it installed already, just test by typing openssl
in your terminal.
If not, on a Mac you can install it using brew install openssl
if you use Homebrew. Otherwise search on Google "how to install openssl on ".
Once OpenSSL is installed, run this command:
openssl req -nodes -new -x509 -keyout server.key -out server.cert
It will as you a few questions. The first is the country name:
Generating a 1024 bit RSA private key...........++++++.........++++++writing new private key to 'server.key'-----You are about to be asked to enter information that will be incorporated into your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter '.', the field will be left blank.-----Country Name (2 letter code) [AU]:
Then your state or province:
State or Province Name (full name) [Some-State]:
your city:
Locality Name (eg, city) []:
and your organization name:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Organizational Unit Name (eg, section) []:
You can leave all of these empty.
Just remember to set this to localhost
:
Common Name (e.g. server FQDN or YOUR name) []: localhost
and to add your email address:
Email Address []:
That’s it! Now you have 2 files in the folder where you ran this command:
server.cert
is the self-signed certificate fileserver.key
is the private key of the certificateBoth files will be needed to establish the HTTPS connection, and depending on how you are going to setup your server, the process to use them will be different.
Those files need to be put in a place reachable by the application, then you need to configure the server to use them.
This is an example using the https
core module and Express:
const https = require('https')const app = express()
app.get('/', (req, res) => { res.send('Hello HTTPS!')})
https.createServer({}, app).listen(3000, () => { console.log('Listening...')})
without adding the certificate, if I connect to https://localhost:3000
this is what the browser will show:
With the certificate in place:
const fs = require('fs')
//...
https.createServer({ key: fs.readFileSync('server.key'), cert: fs.readFileSync('server.cert')}, app).listen(3000, () => { console.log('Listening...')})
Chrome will tell us the certificate is invalid, since it’s self-signed, and will ask us to confirm to continue, but the HTTPS connection will work:
If you run a Node.js application on your own VPS, you need to manage getting an SSL certificate.
Today the standard for doing this is to use Let’s Encrypt and Certbot, a tool from EFF, aka Electronic Frontier Foundation, the leading nonprofit organization focused on privacy, free speech, and in general civil liberties in the digital world.
These are the steps we’ll follow:
Those instructions assume you are using Ubuntu, Debian or any other Linux distribution that uses apt-get
:
sudo add-apt repository ppa:certbot/certbotsudo apt-get updatesudo apt-get install certbot
You can also install Certbot on a Mac to test:
brew install certbot
but you will need to link that to a real domain name, in order for it to be useful.
Now that Certbot is installed, you can invoke it to generate the certificate. You must run this as root:
certbot certonly --manual
or call sudo
sudo certbot certonly --manual
The installer will ask you the domain of your website.
This is the process in detail.
It asks for the email
➜ sudo certbot certonly --manualPassword: XXXXXXXXXXXXXXXXXXSaving debug log to /var/log/letsencrypt/letsencrypt.logPlugins selected: Authenticator manual, Installer NoneEnter email address (used for urgent renewal and security notices) (Enter 'c' tocancel): [email protected]
It asks to accept the ToS:
Please read the Terms of Service athttps://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You mustagree in order to register with the ACME server athttps://acme-v02.api.letsencrypt.org/directory
(A)gree/(C)ancel: A
It asks to share the email address
Would you be willing to share your email address with the Electronic FrontierFoundation, a founding partner of the Let's Encrypt project and the non-profitorganization that develops Certbot? We'd like to send you email about our workencrypting the web, EFF news, campaigns, and ways to support digital freedom.- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -(Y)es/(N)o: Y
And finally we can enter the domain where we want to use the SSL certificate:
Please enter in your domain name(s) (comma and/or space separated) (Enter 'c'to cancel): copesflavio.com
It asks if it’s ok to log your IP:
Obtaining a new certificatePerforming the following challenges:http-01 challenge for copesflavio.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -NOTE: The IP of this machine will be publicly logged as having requested thiscertificate. If you're running certbot in manual mode on a machine that is notyour server, please ensure you're okay with that.
Are you OK with your IP being logged?- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -(Y)es/(N)o: y
And finally we get to the verification phase!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Create a file containing just this data:
TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E.1DzOo_voCOsrpddP_2kpoek2opeko2pke-UAPb21sW1c
And make it available on your web server at this URL:
http://copesflavio.com/.well-known/acme-challenge/TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E
Now let’s leave Certbot alone for a couple minutes.
We need to verify we own the domain, by creating a file named TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E
in the .well-known/acme-challenge/
folder. Pay attention! The weird string I just pasted change every single time.
You’ll need to create the folder and the file, since they do not exist by default.
In this file you need to put the content that Certbot printed:
TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E.1DzOo_voCOsrpddP_2kpoek2opeko2pke-UAPb21sW1c
As for the filename, this string is unique each time you run Certbot.
In order to serve that file from Express, you need to enable serving static files. You can create a static
folder, and add there the .well-known
subfolder, then configure Express like this:
const express = require('express')const app = express()
//...
app.use(express.static(__dirname + '/static', { dotfiles: 'allow' } ))
//...
The dotfiles
option is mandatory otherwise .well-known
, which is a dotfile as it starts with a dot, won't be made visible. This is a security measure, because dotfiles can contain sensitive information and they are better off preserved by default.
Now run the application and make sure the file is reachable from the public internet, and go back to Certbot, which is still running, and press ENTER to go on with the script.
That’s it! If all went well, Certbot created the certificate, and the private key, and made them available in a folder on your computer (and it will tell you which folder, of course).
Now copy/paste the paths into your application, to start using them to serve your requests:
const fs = require('fs')const https = require('https')const app = express()
app.get('/', (req, res) => { res.send('Hello HTTPS!')})
https.createServer({ key: fs.readFileSync('/etc/letsencrypt/path/to/key.pem'), cert: fs.readFileSync('/etc/letsencrypt/path/to/cert.pem'), ca: fs.readFileSync('/etc/letsencrypt/path/to/chain.pem')}, app).listen(443, () => { console.log('Listening...')})
Note that I made this server listen on port 443, so you need to run it with root permissions.
Also, the server is exclusively running in HTTPS, because I used https.createServer()
. You can also run an HTTP server alongside this, by running:
http.createServer(app).listen(80, () => { console.log('Listening...')})
https.createServer({ key: fs.readFileSync('/etc/letsencrypt/path/to/key.pem'), cert: fs.readFileSync('/etc/letsencrypt/path/to/cert.pem'), ca: fs.readFileSync('/etc/letsencrypt/path/to/chain.pem')}, app).listen(443, () => { console.log('Listening...')})
The SSL certificate is not going to be valid for 90 days. You need to set up an automated system for renewing it.
How? Using a cron job.
A cron job is a way to run tasks every interval of time. It can be eery week, every minute, every month.
In our case we’ll run the renewal script twice per day, as recommended in the Certbot documentation.
First find out the absolute path of certbot
on you system. I use type certbot
on macOS to get it, and in my case it's /usr/local/bin/certbot
.
Here’s the script we need to run:
certbot renew
This is the cron job entry:
0 */12 * * * root /usr/local/bin/certbot renew >/dev/null 2>&1
It means run it every 12 hours, every day: at 00:00 and at 12:00.
Tip: I generated this line using https://crontab-generator.org/
Add this script to your crontab, by using the command:
env EDITOR=pico crontab -e
This opens the pico
editor (you can choose the one you prefer). You enter the line, save, and the cron job is installed.
Once this is done, you can see the list of cron jobs active using
crontab -l
If you made this far, that’s awesome! I hope this introduction to Express will get you up to speed with this awesome Node.js library. Remember, you can get a PDF, ePub and Mobi version of this page for an easier reference, or to read it on your Kindle or tablet.