Node.js and Express Tutorial: Building and Securing RESTful APIs
TL;DR: In this article, you will learn how to develop RESTful APIs with Node.js, Express, and Auth0. You will start from scratch, scaffolding a new Node.js project, then you will go through all the steps needed to build a secure API. You can check the full code developed throughout this article in this GitHub repository.
Nội Dung Chính
Prerequisites
To follow along with this article, you will need to have prior knowledge around JavaScript. If you have never used JavaScript before (even for frontend development), you might not understand the article well and it might make sense to learn about it first. If you do have previous experience with JavaScript, but you haven’t used Node.js, don’t worry, you won’t have a hard time here. Although it would be ideal to know a bit about Node.js, you will see that the code and the concepts explained in this article are not complex.
Other than that, you will need to have Node.js and NPM installed in your machine. If you don’t have these, please, follow the instructions over here.
What You Will Build
As mentioned before, in this article, you will start from scratch (i.e., from an empty directory), then you will go through all the steps needed to build a secure RESTful API. The API that you will build will allow clients (third-party applications) to issue requests to manipulate resources. The resources, in this case, will represent ads (as in products or services being advertised) that users will create, retrieve, update, and delete.
If you don’t know what RESTful APIs are or what this term stands for, take a look at this brief definition and explanation of RESTful APIs:
A RESTful API is an Application Programming Interface (API) that uses HTTP verbs like
GET
,PUT
,POST
, andDELETE
to operate data. Also referred to as RESTful web services, RESTful APIs are based on the REpresentational State Transfer (REST) approach, an architectural style that enables developers to manipulate data.
For more information, check the following resources:
Building and Securing RESTful APIs
Now that you know what you will create and what the prerequisites are, it’s time to starting building your application. For starters, open a terminal, move it to the directory where you usually create your projects, and create a new directory there:
mkdir
express-ads-api
Then, move into this new directory and use npm
to scaffold a new project:
npm
init -y
The command above will scaffold the project with some default properties. If you open this directory in a text editor or in an IDE (like Visual Studio Code or WebStorm), you will see that the npm
command you issued created a file called package.json
. Opening this file, you will see the following contents:
{
"name"
:
"express-ads-api"
,
"version"
:
"1.0.0"
,
"description"
:
""
,
"main"
:
"index.js"
,
"scripts"
:
{
"test"
:
"echo \"Error: no test specified\" && exit 1"
}
,
"keywords"
:
[
]
,
"author"
:
""
,
"license"
:
"ISC"
}
Right now, this file is quite short and doesn’t have that much interesting information (it just exposes some properties like the project name
, version
, and description
). However, as you start adding dependencies to your project, the tendency is that this file will grow and get more interesting.
Next, you will create a new directory called src
inside the project root:
mkdir
src
The idea here is to put all your source code (i.e., the JavaScript files) inside this directory. So, after creating this directory, create a new file called index.js
inside it, and add the following code to it:
console.
log
(
'Hello, world!'
)
;
After saving this file, you can head back to your terminal and issue the following command to test it (make sure you are on the project root):
node src
If everything works as expected, you will see “Hello, world!” printed out in your terminal.
Creating your first Express API
Right now, the project you created just logs a static message. As this is not very useful, after building your “Hello, world!” application with Node.js, you can start focusing on creating a RESTful API. For that, the first thing you will need is to install some dependencies. So, head to your terminal and issue the following command:
npm
install
body-parser cors express helmet morgan
This command will install five dependencies in your project:
body
: You will use this dependency to convert the body of incoming requests into JavaScript objects.-
parser
cors
: You will use this dependency to configure Express to add headers stating that your API accepts requests coming from other origins. This is known as Cross-Origin Resource Sharing (CORS).express
: This is the Express library itself.helmet
: This library helps to secure Express APIs by defining various HTTP headers.morgan
: This library adds some logging capabilities to your Express API.
Note: After issuing the command above, you will notice two things in your project. First, the
package.json
file will contain a new property calleddependencies
with all the libraries above. This is how NPM knows what dependencies your project needs. Second, you will notice a new file calledpackage-lock.json
inside the project root. This file helps NPM identify what are the exact libraries you used while developing, so it uses the same ones everywhere (i.e., in other environments).
When NPM finishes installing these dependencies (it might take a few seconds, depending on your internet connection), you can open the index.js
file, and replace its code with the following:
const
express =
require
(
'express'
)
;
const
bodyParser =
require
(
'body-parser'
)
;
const
cors =
require
(
'cors'
)
;
const
helmet =
require
(
'helmet'
)
;
const
morgan =
require
(
'morgan'
)
;
const
app =
express
(
)
;
const
ads =
[
{
title:
'Hello, world (again)!'
}
]
;
app.
use
(
helmet
(
)
)
;
app.
use
(
bodyParser.
json
(
)
)
;
app.
use
(
cors
(
)
)
;
app.
use
(
morgan
(
'combined'
)
)
;
app.
get
(
'/'
,
(
req
,
res)
=>
{
res.
send
(
ads)
;
}
)
;
app.
listen
(
3001
,
(
)
=>
{
console.
log
(
'listening on port 3001'
)
;
}
)
;
The new version of this file starts by importing all the dependencies you installed moments ago, goes through the creation and configuration of a new Express application (const app = express()
), and ends by making this application listen on port 3001
(app.listen(3001, ...)
). Besides that, this code defines two important things:
- an array called
ads
that works, temporarily, as an in-memory database (you will replace this soon); - and an endpoint that listens to HTTP
GET
ads
array.
Note: The code snippet above contains comments that can help you understand each line. If you want to learn more about the middleware being used (i.e., about
helmet
,bodyParser
,cors
, andmorgan
), please, refer to their official documentation.
After updating this file, you can issue node src
again from the project root. Then, in another terminal, you can use curl
to issue an HTTP request to test your API:
curl
http://localhost:3001/
Note: If no verb is explicitly configured (through the
-X
parameter),curl
command will issue an HTTPGET
request.
If you prefer, you can also use a graphical HTTP client like Insomnia or Postman. For example, the screenshot below shows Insomnia after issuing a request to the Express API.
No matter how you decide to issue the request, after receiving it, the application will delegate this request to the app.get('/', ...)
endpoint. Then, as defined, the endpoint will send back to the client the following response (i.e., the ads
array):
[
{
"title"
:
"Hello, world (again)!"
}
]
Integrating Express and MongoDB
When it comes to databases, the most popular choice among Node.js developers is (by far) MongoDB. This database engine allows developers to use a flexible document data model that plays particularly well with Node.js apps. As you will see throughout the article, manipulating a MongoDB database from a Node.js application is easy and efficient.
Before learning about how to make your Express API operate MongoDB though, you will need a database instance. For that, you have several options like installing MongoDB in your machine, running it in a container, or using a cloud provider like MongoDB Atlas. However, to facilitate the process, you will use a package called mongodb-memory-server
that spins up a MongoDB instance programmatically for testing or mocking during development. What is nice about this library is that, by default, it holds the data in memory. Also, you will install the official mongodb
NPM package to make your app interact with this in-memory database.
So, back into your terminal, use npm
to install these packages:
npm
i mongodb-memory-server mongodb
After installing them, create a new directory called database
inside the src
directory and, inside it, create a new file called mongo.js
. Inside this file, add the following code:
const
{
MongoMemoryServer}
=
require
(
'mongodb-memory-server'
)
;
const
{
MongoClient}
=
require
(
'mongodb'
)
;
let
database =
null
;
async
function
startDatabase
(
)
{
const
mongo =
new
MongoMemoryServer
(
)
;
const
mongoDBURL =
await
mongo.
getConnectionString
(
)
;
const
connection =
await
MongoClient.
connect
(
mongoDBURL,
{
useNewUrlParser:
true
}
)
;
database =
connection.
db
(
)
;
}
async
function
getDatabase
(
)
{
if
(
!
database)
await
startDatabase
(
)
;
return
database;
}
module.
exports =
{
getDatabase,
startDatabase,
}
;
As you can see, this file exports
two functions. One to initialize the in-memory database (startDatabase
) and one that returns a reference to it (getDatabase
).
With that in place, create a new file called ads.js
inside the database
directory and add the following code to it:
const
{
getDatabase}
=
require
(
'./mongo'
)
;
const
collectionName =
'ads'
;
async
function
insertAd
(
ad
)
{
const
database =
await
getDatabase
(
)
;
const
{
insertedId}
=
await
database.
collection
(
collectionName)
.
insertOne
(
ad)
;
return
insertedId;
}
async
function
getAds
(
)
{
const
database =
await
getDatabase
(
)
;
return
await
database.
collection
(
collectionName)
.
find
(
{
}
)
.
toArray
(
)
;
}
module.
exports =
{
insertAd,
getAds,
}
;
The ads.js
file is also defining and exporting two functions. The difference though is that this file exports a function that allows you to insert an ad into the database (insertAd
) and one that retrieves all the records persisted there (getAds
). Note that both of these functions use the getDatabase
function exported by the mongo.js
file to get the reference that points to your in-memory database.
After creating this file, open the index.js
file and update it as follows:
const
{
startDatabase}
=
require
(
'./database/mongo'
)
;
const
{
insertAd,
getAds}
=
require
(
'./database/ads'
)
;
app.
get
(
'/'
,
async
(
req
,
res)
=>
{
res.
send
(
await
getAds
(
)
)
;
}
)
;
startDatabase
(
)
.
then
(
async
(
)
=>
{
await
insertAd
(
{
title:
'Hello, now from the in-memory database!'
}
)
;
app.
listen
(
3001
,
async
(
)
=>
{
console.
log
(
'listening on port 3001'
)
;
}
)
;
}
)
;
With this refactoring, you are:
- importing and calling the
startDatabase
function to initialize the in-memory instance before making the Express API listen to requests; - importing and calling the
insertAd
function to create a new ad right after starting the database; - and importing and calling the
getAds
inside the endpoint responsible for theGET
Note that you are replacing the previous implementation of the GET
endpoint to stop returning the static ads
array and to start returning the records available inside the database. As such, you can remove the lines that define the ads
constant.
When you finish with the refactoring, you can stop your API (by hitting control
+ C
), start it again (node src
), and issue the same HTTP request as before (curl http://localhost:3001/
). The difference is that, now, your API will respond with an array that contains an object with two properties: title
(just like before) and _id
(which refers to its primary key on the database).
Now that you have an Express API integrated with MongoDB, it is time to implement the other HTTP verbs (i.e., the other endpoints). In this section, you will add three new endpoints to your API:
- an endpoint responsible for
POST
- an endpoint responsible for
DELETE
- an endpoint responsible for
PUT
To add these endpoints, you will start by defining the functions that will interact with your MongoDB instance. So, open the ads.js
file (it resides inside the database
directory), and update it as follows:
const
{
ObjectID}
=
require
(
'mongodb'
)
;
async
function
deleteAd
(
id
)
{
const
database =
await
getDatabase
(
)
;
await
database.
collection
(
collectionName)
.
deleteOne
(
{
_id:
new
ObjectID
(
id)
,
}
)
;
}
async
function
updateAd
(
id
,
ad)
{
const
database =
await
getDatabase
(
)
;
delete
ad.
_id;
await
database.
collection
(
collectionName)
.
update
(
{
_id:
new
ObjectID
(
id)
,
}
,
{
$set:
{
...
ad,
}
,
}
,
)
;
}
module.
exports =
{
deleteAd,
updateAd,
}
;
Here, you are adding only two new functions (deleteAd
and updateAd
) because you already have a function that allows the insertion of new ads (insertAd
). Note that both new functions need an element called ObjectID
to be able to tell the database which specific element you want to update or delete.
Another important thing to grasp is the object passed to the $set
property on the update
operation. While updating a document in a MongoDB database, you can inform only the properties that have changed and omit whatever remains the same. For example, if you have an object in your database with fields called name
, phone
, and address
, you can pass to $set
only the phone
property to change it while leaving the rest untouched. If this is not clear yet, you will see this is in action in a bit.
After refactoring this file, you will have to open the index.js
file and update it as follows:
const
{
deleteAd,
updateAd}
=
require
(
'./database/ads'
)
;
app.
post
(
'/'
,
async
(
req
,
res)
=>
{
const
newAd =
req.
body;
await
insertAd
(
newAd)
;
res.
send
(
{
message:
'New ad inserted.'
}
)
;
}
)
;
app.
delete
(
'/:id'
,
async
(
req
,
res)
=>
{
await
deleteAd
(
req.
params.
id)
;
res.
send
(
{
message:
'Ad removed.'
}
)
;
}
)
;
app.
put
(
'/:id'
,
async
(
req
,
res)
=>
{
const
updatedAd =
req.
body;
await
updateAd
(
req.
params.
id,
updatedAd)
;
res.
send
(
{
message:
'Ad updated.'
}
)
;
}
)
;
On the new version of this file, you are adding the endpoints responsible for the three HTTP verbs mentioned before (POST
, DELETE
, and PUT
). What is important to note here is that you are using Express route parameters to be able to fetch, from the URL requested, the id
of the ad you want to delete or update (/:id
). Also, as you can see on both the post
and put
endpoints, you are getting the details of the ad being inserted or updated from the request body (req.body
).
After changing this file, you can stop your API (by hitting control
+ C
), start it again (node src
), and issue some HTTP requests (as presented on the following code snippet) to test the new endpoints.
curl
-X POST -H 'Content-Type: application/json'
-d '{
"title": "Pizza",
"price": 10.5
}'
http://localhost:3001/
curl
http://localhost:3001/
ID
=
${AD_ID}
curl
-X PUT -H 'Content-Type: application/json'
-d '{
"price": 12.5
}'
http://localhost:3001/$ID
curl
-X DELETE http://localhost:3001/$ID
Note: If you are using the code snippet above, right after inserting a new ad, you are issuing a request to get all ads persisted on the database. Use the result of this request to copy the
_id
property of the new ad and use it to replace the${AD_ID}
placeholder.
If everything works as expected, the first request will persist a new ad in your API, the second one will return all the ads persisted there, the third request will update the price of the new ad (from 10.5
to 12.5
), and the fourth one will remove the ad from the
On the PUT
request, you can see that you are passing just one field on the request body (price
). As the $set
object passed to the update
operation is using this exact body to update the ad, the title
of the ad (which is “Pizza”) will not be changed. Neat, right?
Securing Express APIs with Auth0
Right now, you have an Express API that exposes endpoints that allow clients to insert, update, delete, and retrieve ads. This is a nice start, but you could use some security, right?
For example, let’s say that you want to enable all users (no matter if they are visitors or if they are authenticated) to list ads, but you want only authenticated users to be able to insert, update, and delete objects. How would you do this? An easy answer to this question is “by using Auth0”.
As you will see in this section, securing Express APIs with Auth0 is very easy. For starters, you will need to sign up to Auth0 so you can integrate it into your API. If you already have an existing account, you can use it without a problem. If you do not have one, now is a good time to sign up for a free Auth0 account. What is cool about Auth0 is that, with your free account, you will have access to the following features:
Try out the most powerful authentication platform for free. Get started →
After signing up, you will have to create an Auth0 API to represent your Express project. So, head to the APIs section of your Auth0 Dashboard and click on the Create API button. When you click on this button, Auth0 will show you a dialog where it will ask you for three things:
- Name: A friendly name for your API. As this is just used inside the Auth0 Dashboard itself, don’t worry much about this value (e.g., you can use something like “Express APIs Tutorial”).
- Identifier: A logical identifier for the API you are creating. As Auth0 recommends using an URL-like value, you can add something like
https
here (although this looks like an URL, Auth0 will never call it).:
/
/
ads
-
api
- Signing Algorithm: Leave this set to
RS256
After filling this form, click on the Create button. Then, back to the terminal, issue the following command:
npm
i express-jwt jwks-rsa
Here, you are installing two new libraries:
express
: A middleware that validates JSON Web Tokens (JWTs) and sets the-
jwt
req
with its attributes..
user
jwks
: A library to retrieve RSA public keys from a JWKS (JSON Web Key Set) endpoint.-
rsa
After that, open the ./src/index.js
file and import these libraries as follows:
const
jwt =
require
(
'express-jwt'
)
;
const
jwksRsa =
require
(
'jwks-rsa'
)
;
Still on this file, create the following constant (checkJwt
) right before the POST
endpoint (app.post
):
const
checkJwt =
jwt
(
{
secret:
jwksRsa.
expressJwtSecret
(
{
cache:
true
,
rateLimit:
true
,
jwksRequestsPerMinute:
5
,
jwksUri:
`
https://<AUTH0_DOMAIN>/.well-known/jwks.json
`
}
)
,
audience:
'<API_IDENTIFIER>'
,
issuer:
`
https://<AUTH0_DOMAIN>/
`
,
algorithms:
[
'RS256'
]
}
)
;
This constant is actually an Express middleware that will validate access tokens. Note that, to make it work, you will have to replace the <API_IDENTIFIER>
placeholder with the identifier of the Auth0 API you created (e.g., https://ads-api
). Also, you will have to replace <AUTH0_DOMAIN>
with your Auth0 domain (e.g., blog-samples.auth0.com
).
Note: Not sure what your Auth0 domain is? When you create a new account with Auth0, you are asked to pick a name for your Tenant. This name, appended with
auth0.com
, will be your Auth0 domain. For more information, please, check the Learn the Basic doc.
With that in place, you can secure the post
, put
, and delete
endpoints by adding the following line right before their definition:
app.
use
(
checkJwt)
;
On the code snippet above, you are configuring the Express application to use
the checkJwt
middleware. Note that, as you are defining it after the get
endpoint, the checkJwt
middleware will not intercept requests to this endpoint. In the same way, as you are defining it before the post
, delete
, and put
endpoints, the checkJwt
middleware will intercept requests to them.
After making this change, restart your API (by hitting control
+ C
and then issuing node src
to start it again), and issue the following request to confirm that the get
endpoint is still public:
curl
http://localhost:3001
If everything works as expected, you will still be able to fetch the ads from this endpoint. However, if you try to issue requests to any other endpoint, you will get an error saying that “No authorization token was found”:
curl
-X POST -H 'Content-Type: application/json'
-d '{
"title": "Pizza",
"price": 10.5
}'
http://localhost:3001/
To be able to use these endpoints again, you will need an access token. The process of getting a token will depend on what type of client you are dealing with. This is out of scope here but, if you are dealing with a SPA application (like those created with React, Angular, and Vue.js), you can use the auth0-js
NPM library. If you are dealing with some other type of client (e.g., regular web application or native application), check the Auth0’s docs for more info.
Nevertheless, to see the whole thing in action, you can head back to your Auth0 Dashboard, open the API you created before, and move to the Test section. On this section, you will see a button called Copy Token that will provide you a temporary token that you can use to test your API.
So, click on this button and then use your HTTP client to issue a request to your API with the test token:
TOKEN
=
eyJ..
.DRA
curl
-X POST -H 'Authorization: Bearer '
$TOKEN
-H 'Content-Type: application/json'
-d '{
"title": "Pizza",
"price": 10.5
}'
http://localhost:3001/
If everything works as expected, you will be able to use your API endpoints again. Awesome, huh?
Conclusion
In this article, you learned about how easy it is to develop RESTful APIs with Express and Node.js. More specifically, you started by using npm
to scaffold a brand new application. After that, you used Express to expose API endpoints to manipulate ads. Then, in the end, you learned how to secure your API with Auth0.
With this setup, you are ready to move on and start building your production-ready APIs backed by Node.js, Express, Mongo, and Auth0. However, before doing so, one important thing you might want to learn about is express-validator
, an Express middleware that helps you validate data sent by users. For that, check this article we recently published.
Was this fast (and fun) enough for you? Let us know in the comments section below.