Building a RESTFUL Express Node Application

With Node.js, Express, and EJS Published by Nathan A. Wilcox on July 31, 2020

Time to read: 15 minutes. Level: Beginner

Crash Course in RESTing

REST (Representational state transfer) is a buzzword thrown around a lot. But do you really understand the philosophy behind this phrase? It is not so much a technology, but a way to view development in general. With REST, URLs do not point to web pages. They represent resources. And the actions we want to perform with these resources is derived from the HTTP method we use (GET, POST, PUT, DELETE).

For example, the URL /profile/Nathan could represent a profile called 'Nathan'. If you request this URL using a GET method, the application could select an html view to display the profile to the requester. If you send a request to this URL using the PUT method, this tells the application you want to update the profile, so it may look for form data in the request, and then update the profile with your provided data. We could finally delete the profile by sending a request to /profile/Nathan with the DELETE method.

The key take away is, in all scenarios, the URL doesn't change, only the method we use changes. This is the intent behind REST. URLs map to resources, and how we access them is based on the HTTP method.


How to be RESTful with Node.js and Express

We are going to make a sample application that manages online profiles. We will start by creating controller that can handle any type of action we could make against our profile (Create, Read, Update, Delete). Persisting your data to a data store is not within the scope of this article, so for demonstration purposes, we will use some placeholder values and log messages to simulate what the end result would be. If you are following along at home, place your profileController.js file in a /controllers folder in your application.


//controller code at [PROJECT ROOT]/controllers/profileController.js
const profileController = {
	
  readProfile: function(res, req) {
    console.log("read existing profile")
    
    //placeholder for logic to determine if user is authenticated or if object is found
    let loggedIn = false
    let noObjectFound = false

    //Decide how to represent this object.
    //If object not found, render a form to create one.
    //Else, if logged in, give form to edit the profile.
    //Else, give view to only read profile.
    if(noObjectFound) {
      res.render('new')
    }
    else if(loggedIn) {
      res.render('edit')
    }
    else {
      res.render('readonly')
    }
  },
	
  createProfile: function(res, req) {
    console.log("create new profile")
    
    //place holder. once you persist an object to a datastore, 
    //fetch id for redirecting back to that object.
    let profileId = 1234

    //redirect back to profile page
    res.redirect(`/profile/{profileId}`)
  },
	
  updateProfile: function(res, req) {
    console.log("update existing profile")

    //place holder. once you persist an object to a datastore, 
    //fetch id for redirecting back to that object.
    let profileId = 1234

    //redirect back to profile page
    res.redirect(`/profile/{profileId}`)
  },
	
  deleteProfile: function(res, req) {
    console.log("delete existing profile")

    //redirect back to home.
    res.redirect('/')
  }
}

module.exports = profileController

We have an action for each CRUD operation.

  1. The CREATE action simulates the persisting of an object and redirecting back to the READ action. 
  2. The READ action decides the best way to represent an object based on user permission or if the object exists.
  3. The UPDATE action simulates updating an object and redirecting back to the READ action.
  4. The DELETE action simulates deleting an object and redirecting back to /.

We can now create our routing logic that will route requests to each action based on the HTTP method.


//app code at [PROJECT ROOT]/app.js
const express = require('express');
const bodyParser = require('body-parser')

//in my project structure, I put my controllers in a controller folder.
//When importing the module, I include the relative path to the controller.
const profileController= require('./controllers/profileController')

const app = express();
const port = 3000;

//middleware to parse form data
//use npm to install body-parser module
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())

//crud routes to manipulate profile resource.
//app.[HTTP METHOD]('[route]', [handler])
app.get(    '/profile/:id', profileController.readProfile)
app.post(   '/profile',     profileController.createProfile)
app.put(    '/profile/:id', profileController.updateProfile)
app.delete( '/profile/:id', profileController.deleteProfile)

app.listen(port, () => console.log(`REST API listening on port ${port}!`));

Sending REST calls to the server

For viewing profiles, we are going to use the GET method. To do this, we are going to use an anchor tag that points to a specific profile by using the profile/:id pattern found in our routing logic. We can simply print the profile id in the markup like so. (Note: The following example uses EJS. Typically, this would be on an index page in a for loop to iterate a collection, printing a link for each instance.):


<a href="/profile/<%= profile._id %>Get My Profile<a>

This will add a link to the page that will send a GET request to the server. The server will route to the readProfile controller action, and based on if the user is authenticated or if the object does not exist, it will then decide how to represent that object to the user (new, edit, or readonly views).

Notice how the user does not have to specifically ask for a new profile form or the edit page. They simply asked to view a profile, and the server decided if that the resource needs to be created, or if they should have the ability to edit or simply view a read-only version. Now we are really starting to see the philosophy of REST in action. 


POST New Profiles

Let's say we clicked a link and the server couldn't find the profile, so it sent us a view to create a new profile. This contained a form using the POST method.


<form method="POST" action="/profile">
  ...
  <input type="text" id="fullname" name="fullname" />
  <button type="submit">Store Profile</button>
</form>

When we click submit, the server sees the request with a POST method, so it routes to the createProfile action. Here we can persist the data to a database and then redirect to a read-only or edit view of the profile.

This method does not include the :id portion of the URL because we are creating a new resource. Another way you could design your routing logic is to use the URL /profile/new when creating new profiles. This is a very human friendly URL that is very clear in its intentions. It also frees up using the /profile URL to display an index page that lists all the profiles


PUT AND DELETE

Interestingly enough, PUT and DELETE are not supported by HTML, so we are going to have to make some customization for these methods to function correction. Again, since this is REST, when we want to update or delete our profiles,we will still be sending our requests to the same URL, /profiles/:id. Always remember, each URL should represent a single resource, and how we manipulate that resource is based on the methods we use.

We cannot simply set our form method to PUT or DELETE. The browser will change it back to POST against our will. In order for our forms to submit using PUT and DELETE, we need to intercept the request before it is routed to a controller, and change the method back to our desired value. For this, we need to create a custom express middleware, and listen for requests that need to override the HTTP method using a value we are going to supply in the querystring. See below:


...
//middleware to parse form data
//use npm to install body-parser module
app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())
app.use( function( req, res, next ) {

  if ( req.query._method == 'DELETE' ) {
    req.method = 'DELETE';
    req.url = req.path;
  } 
  else  if( req.query._method == 'PUT' ) {
    req.method = 'PUT';
    req.url = req.path;
  }     
  
  next(); 
});

//crud routes to manipulate profile resource.
//app.[HTTP METHOD]('[route]', [handler])
app.get(    '/profile/:id', profileController.readProfile)
...

Before we route our request to any controllers, we listen for requests that contain '_method' in the querystring. If we find a value of _method=PUT or _method=DELETE, we set the request method according, and then call the next() function to continue processing the request.

To include our desired method in the request, we need to add a querystring (?_method=PUT) to our form action including the HTTP method we want to use on the serer. See below:


<form method="POST" action="/profile/<= profile._id %>?_method=PUT">
  ...
  <input type="text" id="fullname" name="fullname" value="<= profile.fullname %>" />
  <button type="submit">Update Profile</button>
</form>

Here is a sample edit view we may get from requesting our profile. In our form action, we are including the URL of the profile want update, along with a querystring naming the method we want to use, PUT. When the submit button is clicked and the request is received on the server, our custom middleware will change the method to PUT, and route us to the update action. 

We can also use our custom middleware for deleting profiles. Typically, an anchor tag only allows the GET method, but with our custom middleware we created, we can add ?_method=DELETE to the href, so when this link is clicked, the method gets updated on the server, and the request is routed to the delete action.


<a href="/profile/<%=profile._id%>?_method=DELETE">Delete My Profile<a>

We can now submit requests to read, update, and delete our profile, all with only a single URL (ex. /profile/Nathan). The only thing that is different is the method being used. This is REST in its most basic form.


Bonus Exercise

We have seen how to use standard forms and anchor tags to send requests to the server in a RESTful way. As an exercise, try to do the same using jQuery based ajax requests. And instead of returning html responses, return JSON data.

Imagine creating a single profile service that other applications are able to consume. Having your application return JSON data will make it much easier for those applications to integrate with your API


Summary

In this article, we created a REST profile application that has basic CRUD functions. We can read a profile by sending a GET request to the URL /profile/Nathan, update a profile by sending a PUT request to the URL /profile/Nathan, and delete a profile by sending a DELETE request to the URL /profile/Nathan. This is the basic strategy of making a REST application. The application manages resources, each with a unique URL. Based on the HTTP method requested, the application will generate the appropriate response. 


Comments