Bernhard Häussermann
Posted on August 4, 2021
This article is the first of a series outlining the steps to build a REST API from scratch that runs in Node.js using the Express web application framework. In this article, we will show how to set up the project. The following articles will build on this by adding features such as request / response validation and a Swagger UI page for online documentation.
Project setup
The configuration needed for following along in your own project is minimal. All that is required to get started is a package.json file generated using npm init
.
Ensure that "type": "module"
is set within the package.json file. This declares our package as an ES 6 module so we can use import
syntax to import packages in our source code.
Add a basic web server
Add the Express package as a run-time dependency:
npm install --save express
Then create a new file server.js in a folder named src with the following contents:
And just like that we have a working web endpoint listening at port 3000!
In the code above, we have defined a single route which we can use to test that the service is running.
Testing that our endpoint works is easy as:
- Run node
src/server.js
- Using your favourite REST API testing tool (I recommend Postman), request GET
localhost:3000/greeting
We should get a 200-response containing some text as a JSON string.
Adding automatic restarts
Running our server script as above means that whenever a change is made to a source file, we need to manually stop and start the program for the changes to take effect. This is easy to fix, thanks to a simple tool called nodemon. We can easily add a script that will restart our application whenever a source file is changed.
First, we add nodemon as a development dependency to the project:
npm install --save-dev nodemon
We then define the following set of scripts in package.json:
"scripts": {
"run": "node src/server.js",
"run:watch": "nodemon src/server.js --watch src",
"start": "npm run run:watch"
},
The run script will run the API without automatic restarts as before if we execute npm run run
The run:watch script will run the API, restarting it whenever any file inside the src folder changes.
The start script will simply run the run:watch script but can be executed merely as npm start
Structuring the code based on REST resources
Most REST APIs have their routes arranged based on a number of resources. We will define employees as a REST resource with CRUD (create, retrieve, update, delete) operations. Keeping with REST conventions, we will define the following routes:
- GET
/employees
: Return the list of all employees. - GET
/employees/{employee-id}
: Gets the single employee having the ID{employee-id}
. Return a 404 (Not Found) response code if no employee with the specified ID was found. - POST
/employees
: Add a new employee entry. - PUT
/employees/{employee-id}
: Update the details of the employee having the ID{employee-id}
. - DELETE
/employees/{employee-id}
: Delete the employee having the ID{employee-id}
.
If we keep on defining all our routes and the code implementing them directly in server.js, the code will quickly become unmanageable. To help keep the code organized, I recommend defining each REST resource's routes in one file and implementing them in another. We call the file defining the routes the "controller" and the file containing the implementation the "service".
Implementing the employees resource leads to the following folder structure:
src
controllers
employees-controller.js
services
employees-service.js
server.js
Here is a simple implementation of employees-service.js.
Whereas in a typical application the objects would be persisted in some kind of a database, we store the list of employees in memory for simplicity's sake.
The EmployeeNotFoundError
class is defined in a file named employee-not-found-error.js as:
Note that EmployeesService
does not contain any logic that relates to REST notions like query parameters, response statuses etc. The EmployeesService
is concerned solely with the details of how employees are persisted. This is in adherence to the Single-responsibility principle. It also makes the class easier to test using some testing framework.
The EmployeesController
class deals with the REST-related specifics and hooks up the REST routes to their respective implementations in the employees service:
Note the block-comment before the registerRoutes()
method. This is a JSDoc comment that specifies descriptions to use when generating documentation using JSDoc. However, in this case, we add the block-comment only to inform our IDE of the expected types of the method's parameters. Visual Studio Code, for instance, has built-in support for JSDoc and will interpret the type-declarations of the app
and controller
parameters inside the block-comment to inform its IntelliSense and code completion functionality.
We define the ExpressError
class to represent a REST error which is to be handled by a generic error route handler function in server.js:
Finally, we make the following changes to server.js:
- To register the routes, we now simply call
registerRoutes()
passing in the Express application and a new instance ofEmployeesService
. - We also add a route handler for returning the proper response when an error is thrown.
- To parse the request body of the POST and PUT operations as JSON payloads, we add the statement
app.use(express.json())
We can now use our favourite REST client to test the different routes to verify the behaviour is as expected:
-
Get all employees
GET localhost:3000/employees
-
Get employee 1
GET localhost:3000/employees/1
-
Get employee 2 (doesn't exist)
GET localhost:3000/employees/2
-
Update employee 1's first name
PUT localhost:3000/employees/1 { "firstName": "André" }
-
Add a new employee
POST localhost:3000/employees { "lastName": "King", "firstName": "Robert", "title": "Sales Representative" }
-
Delete employee
DELETE localhost:3000/employees/2
In conclusion
Getting a REST API off the ground using Node.js and Express is relatively straightforward to do, and by defining separate controller and service classes for each type of API resource, we keep the REST-specific details separate from the underlying implementation details of each operation.
Future posts will show how we can quickly add middleware such as request / response validation and Swagger documentation thanks to Node.js packages that are available for this.
The code for the API developed in this article is available on GitHub here.
Posted on August 4, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.