API Development and Routing with Node.js and Express

Paul Halliday

This article will be looking at how we can handle API routes inside of a Node.js and Express project. As always, we’ll start with a brand new Express project and progressively enhance it from the ground up.

What exactly are routes?

Routes are ways that we can handle user navigation to various URLs throughout our application. For example, if we wanted to greet a user with a Hello, World! message on the home page, we may do something like this:

const express = require('express')
const app = express()

app.get('/', function (req, res) {
  res.send('Hello, World!')
})

Without jumping into too much detail at this point, it’s important to understand that we’re not just limited to get responses. We can use other HTTP request methods such as post, put, delete, merge, patch and various others. You can see a full list of potential routing methods here: Express routing methods

So, you could imagine if we wanted to give a different response to the /about page of our application, we’d do something extremely similar:

const express = require('express')
const app = express()

app.get('/', function (req, res) {
  res.send('Hello, World!')
})

app.get('/about', function (req, res) {
  res.send('About, World!')
})

Project setup

Now that you understand the basics, let’s start to investigate these concepts further as we create our own Express application that involves multiple routes.

Ensure you have Node.js installed from Node.js prior to following the next steps. Run the following in your terminal:

# Create a new folder for our project
# here we call it node-express-routing,
# but you can give it any name you'd like
$ mkdir node-express-routing

# Change directory
$ cd node-express-routing

# Initialise a new Node project with defaults
$ npm init -y

# Create our entry file
$ touch index.js

# Install Express
$ npm install express --save
# nodemon will also come in handy
$ npm install -D nodemon

I like to use nodemon to continually restart the Node.js project whenever our index.js or other JavaScript file(s) change. It’s usually a good idea to create a script that runs this inside of your project like so:

{
  "name": "node-express-routing",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon"
  },
  "keywords": [],
  "author": "Paul Halliday",
  "license": "MIT"
}

We can now run npm start or yarn start and our project will be up and running inside of the terminal.

Routes Within Express

We can now create our Express application and have it run on a port that we specify:

index.js

const express = require('express');

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

app.listen(PORT, () => console.log(`Express server currently running on port ${PORT}`));

Our application is loaded on http://localhost:3000. If we navigate to that inside of your browser, you’ll notice that we get the following error:

Cannot GET /

Your first route

That’s because we haven’t specified a default GET route for /, the home page. Let’s do that:

app.get(`/`, (request, response) => {
  response.send('Hello, /');
});

Hello, /

We’re using app.get('/') to specify that we’d like to create a route handler for the / route. At this point, we have access to the request and response variables.

The request variable contains information about the request, for example, if we log out the request.url we’ll see / in the console:

app.get(`/`, (request, response) => {
  console.log(request.url);
  response.send('Hello, /');
});

We can do much more with request and I advise you to look into the documentation on this for further information: Request documentation

We’re able to respond back to the HTTP request with response. Although we’re simply sending plain text back to the browser at this stage, we can also modify this to send HTML instead:

app.get(`/`, (request, response) => {
  response.send(`
    <div>
      <h1>Todo List</h1>
      <ul>
        <li style="text-decoration:line-through">Learn about Express routing</li>
        <li style="text-decoration:line-through">Create my own routes</li>
      </ul>
    </div>
  `);
});

Response - sending across HTML

This works because response.send() can take in either a Buffer object, a String, a JavaScript object, or an Array. Express handles these inputs differently and changes the Content-Type header to match, as ours is a String type, it’s set to text/html. If you’d like more information on this, check out the documentation here: response.send documentation

Working with Endpoints Using Postman

How do we investigate our route in detail? Well, we could use curl to interface with our API at the command line:

$ curl http://localhost:3000

  <div>
    <h1>Todo List</h1>
    <ul>
      <li style="text-decoration:line-through">Learn about Express routing</li>
      <li style="text-decoration:line-through">Create my own routes</li>
    </ul>
  </div>

curl is absolutely a great way to work with our API, but I prefer using Postman. It’s a graphical tool that provides us with powerful features related to API development and design.

Download Postman for your environment by navigating to Postman downloads. You should then be able to test our application by pointing it to our URLs:

Exploring our routes from Postman

We’re making a new request at http://localhost:3000 and can see the response Body, Headers, Cookies and so on. We’ll be using this to test this and other HTTP verbs throughout the rest of the article.

GET, POST, PUT and DELETE with data

We now have a basic understanding of how to create a new route using the HTTP GET verb. Let’s look at how we can take advantage of the various route types with local data. We’ll start with route parameters:

Route parameters

Create a new route at app.get('/accounts') and app.get('/accounts/:id') with some mock data:

let accounts = [];

app.get(`/accounts`, (request, response) => {
  response.json(accounts);
});

app.get(`/accounts/:id`, (request, response) => {
  const accountId = Number(request.params.id);
  const getAccount = accounts.find((account) => account.id === accountId);

  if (!getAccount) {
    response.status(500).send('Account not found.')
  } else {
    response.json(getAccount);
  }
});

We’re now able to use our API to filter for /accounts/:id. When we describe a route that has a :parameterName, Express will consider this as a user inputted parameter and match for that.

As a result of this, if we query for http://localhost:3000/accounts we get all of the accounts in our array:

[
  {
    "id": 1,
    "username": "paulhal",
    "role": "admin"
  },
  {
    "id": 2,
    "username": "johndoe",
    "role": "guest"
  },
  {
    "id": 3,
    "username": "sarahjane",
    "role": "guest"
  }
]

If we instead request http://localhost:3000/accounts/3, we only get one account that matches the id parameter:

{
  "id": 3,
  "username": "sarahjane",
  "role": "guest"
}

Adding the bodyParser Middleware

Let’s add a new route using app.post which uses the request body to add new items to our array. In order to parse the incoming body content, we’ll need to install and set up the body-parser middleware:

$ npm install --save body-parser

Next, tell Express we want to use bodyParser:

const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

POST

If we define the app.post('/accounts') route, we’ll be able to push the incoming body content into our accounts array and return the set:

app.post(`/accounts`, (request, response) => {
  const incomingAccount = request.body;
  
  accounts.push(incomingAccount);
  
  response.json(accounts);
})

We can use Postman to send a POST request to http://localhost/3000/accounts with the following JSON body content:

{
	"id": 4,
	"username": "davesmith",
	"role": "admin"
}

As we’re returning the new accounts with response.json(accounts) the returned body is:

[
  {
    "id": 1,
    "username": "paulhal",
    "role": "admin"
  },
  {
    "id": 2,
    "username": "johndoe",
    "role": "guest"
  },
  {
    "id": 3,
    "username": "sarahjane",
    "role": "guest"
  },
  {
    "id": 4,
    "username": "davesmith",
    "role": "admin"
  }
]

PUT

We can edit/update a particular account by defining a put route. This is similar to what we have done with other routes thus far:

app.put(`/accounts/:id`, (request, response) => {
  const accountId = Number(request.params.id);
  const body = request.body;
  const account = accounts.find((account) => account.id === accountId);
  const index = accounts.indexOf(account);

  if (!account) {
    response.status(500).send('Account not found.');
  } else {
    const updatedAccount = { ...account, ...body };

    accounts[index] = updatedAccount;

    response.send(updatedAccount);
  }
});

We’re now able to change items inside of the accounts array. It’s a simple implementation that just combines / overwrites the initial object with the new addition.

If we set the request URL to http://localhost:3000/accounts/1 and JSON request body to:

{
	"role": "guest"
}

We can see both by the response body and making a GET request to http://localhost:3000/accounts that the role has been updated to guest from admin:

{
  "id": 1,
  "username": "paulhal",
  "role": "guest"
}

DELETE

Items can be deleted using app.delete. Let’s take a look at how we can implement this inside of our application:

app.delete(`/accounts/:id`, (request, response) => {
  const accountId = Number(request.params.id);
  const newAccounts = accounts.filter((account) => account.id != accountId);

  if (!newAccounts) {
    response.status(500).send('Account not found.');
  } else {
    accounts = newAccounts;
    response.send(accounts);
  }
});

Once again, if we send a DELETE request to http://localhost:3000/accounts/1 this will remove the account with the id of 1 from the accounts array.

We can confirm that this works as intended by making a GET request to http://localhost:3000/accounts.

Summary

Phew! This article looked at how we can use Express to create common API routes using HTTP verbs such as GET, POST, PUT, and DELETE.

Further improvements to this would be to include more data validation, security, and the use of a remote database instead of local state. Stay tuned for future articles where we tackle that!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#