How to Build an API with Node and Express

Node JS logo

With the evolution towards API-first software development, chances are you’ll find yourself adding onto an existing API or building a new one. In this article, I’ll walk you through exactly how to build an API with Node and Express.

In future articles, we’ll Dockerize the application and database, configure CircleCI for automatic builds, and set up a continuous delivery pipeline using AWS.

To follow this tutorial, you’ll need to have Node.js installed.

Create the Shell

Open your console, then create and enter a new directory. I’ll be using the BSG API as our example. Feel free to do the same, or use your own name.

Screenshot of a console, making and entering a new directory

Now, run npm init to initialize your project. Go ahead and accept the defaults, with the exception of the entry point. Set that value to server.js, since we’ll use npm start to test the application.

Here’s what I chose:

  • package name: bsgapi
  • version: 1.0.0
  • description: An API to access information about Battlestar Galactica
  • entry point: server.js
  • test command:
  • git repository:
  • keywords:
  • author: Paul Miller
  • license: ISC
Creating a new app with npm init.

npm will create a simple package.json file for you.

Next, run npm install express:

Installing the Express module using npm.

This command will install the express package, a web application framework perfect for building APIs.

Now, create a new file named server.js. In Bash, you can do this by entering touch server.js in your console. Next, open server.js with your favorite text editor. I love Sublime Text for its relatively light profile and ease-of-use, although something as simple as Notepad could work (but why?!).

Let’s start with something simple to make sure our app works. Paste the following into your server.js file:

'use strict'; const express = require('express'); const http = require('http'); // Specify the port so a random port doesn't get assigned const APP_PORT = 3000; // Create a new Express application const app = express(); const startServer = () => { // Create a server object that will listen on port 3000 (APP_PORT) const server = http.createServer(app).listen(APP_PORT, function() { console.log('Server app running on port ' + APP_PORT); }); } // Call the function that starts the server startServer();

Save your changes. In your console, type npm start and press <ENTER>. If all goes well, you should see something like this:

Using npm start to start a Node server app

Not very exciting, but it proves that our basic app works.

Add Logging

Let’s start by adding logging. We’ll use Morgan to log all requests, creating a new log file every day. We’ll get this out of the way now, then start building our routes.

If you haven’t already, stop your app by pressing <CTRL>+<C>.

Install morgan, which we’ll use to log requests, and the rotating-file-stream module, by running the following command in your console: npm install morgan rotating-file-stream.

Require these files in server.js by adding the following lines below const http = require('http');:

const morgan = require('morgan'); const path = require('path'); const rfs = require('rotating-file-stream');

Update your startServer function to add the rotating file stream, set up the logger:

const startServer = () => { // Create a rotating write stream, with a new log every day const accessLogStream = rfs.createStream('access.log', { interval: '1d', path: path.join(__dirname, 'log') }); // Set up the logger app.use(morgan('combined', { stream: accessLogStream })); // Create a server object that will listen on port 3000 (APP_PORT) const server = http.createServer(app).listen(APP_PORT, function() { console.log('Server app running on port ' + APP_PORT); }); }

We’ll verify our logs are working after we create our first route.

Creating a Loader

I like to keep each file in my repository as neat and clean as possible. Rather than clutter up server.js with all of our routes, we’ll take the following approach:

  1. Create an API folder to store individual routes
  2. Create a loader to load the API folder

We’ll start by adding a simple route our users can call to check the status of our API. If, for example, our API was hosted at mycoolapi.com, then calling mycoolapi.com/api/v1/status will result in a message that says “OK.”

Create a new folder in your project named api. Inside this folder, create a file named index.js. Paste the following code into this new file:

const express = require('express'); const router = express.Router(); // API version 1 router.use('/api/v1', require('./v1')); module.exports = router;

Inside your API folder, create a folder named v1. Inside that folder, create a file named status.js. Paste the following code:

const express = require('express'); const router = express.Router(); router.route('/').post(postRouteHandler); router.route('/').get(postRouteHandler); function postRouteHandler(req, res) { res.status(200).json({message: "OK"}); } module.exports = router;

You may notice that the two router.route statements are almost identical. This is because we want our API to respond to both GET and POST requests to our status route.

Finally, add an index.js file inside your v1 folder telling Express where to find the code handling the status route:

const express = require('express'); const router = express.Router(); router.use('/status', require('./status')); module.exports = router;

That’s it for the api directory for now. Let’s add our loader.

Create a new folder named loaders. Inside this folder, create a file named express.js. Paste the following code:

const express = require('express'); module.exports = () => { const app = express(); app.set('port', 3000); app.use('/', require('../api')); return app; };

This file will create the app and direct requests to the various files in the api folder.

Your file structure should look like this:

Build an API with Node - basic file structure

It might seem like overkill now, but it will make things a lot easier as our API grows.

Finally, let’s update server.js to take advantage of our new structure. Replace const app = express() with const app = require('./loaders/express')(). Here’s the complete file for your reference:

'use strict'; const express = require('express'); const http = require('http'); const morgan = require('morgan'); const path = require('path'); const rfs = require('rotating-file-stream'); // Specify the port so a random port doesn't get assigned const APP_PORT = 3000; const app = require('./loaders/express')(); const startServer = () => { // Create a rotating write stream, with a new log every day const accessLogStream = rfs.createStream('access.log', { interval: '1d', path: path.join(__dirname, 'log') }); // Set up the logger app.use(morgan('combined', { stream: accessLogStream })); // Create a server object that will listen on port 3000 (APP_PORT) const server = http.createServer(app).listen(APP_PORT, function() { console.log('Server app running on port ' + APP_PORT); }); } // Call the function that starts the server startServer();

Save your work, then run npm start in your console. Your app should start without an error. Next, navigate to http://localhost:3000/api/v1/status in your browser. You should be rewarded with the following:

Congratulations! The basic API is now working!

Adding Routes

Let’s add a few routes to make this API a bit more interesting.

Cylons Route

We’ll start with a Cylons route. Our endpoint will be a GET request, which will return a list of Cylons.

Quick aside: The difference between routes and endpoints can be confusing. Here’s an explanation of the difference, courtesy of Jon Portella:

3 different concepts here:

* Resource: {id: 42, type: employee, company: 5}
* Route: localhost:8080/employees/42
* Endpoint: GET localhost:8080/employees/42

You can have different endpoints for the same route, such as DELETE localhost:8080/employees/42. So endpoints are basically actions.

Also you can access the same resource by different routes such as localhost:8080/companies/5/employees/42. So a route is way to locate a resource.

We’ll set up our data source first, then add the code for the route.

API data is typically returned from a database. We’ll add one in the future, but for now let’s just keep a small data set here in the code. We’ll keep our data files separate from our API code so they’re easy to delete once we switch over to a database.

Create a new folder in the root of your project named data. Inside this folder, create a file named cylons.js. Copy and paste the following into that file:

const cylons = [ { id: 1, identifier: "Zoe-R", alias: "Zoe Graystone", model: "U-87 Cyber Combat Unit" }, { id: 2, identifier: "One", alias: "John Cavil", model: "Humanoid" }, { id: 3, identifier: "Two", alias: "Leoben Conoy", model: "Humanoid" }, { id: 4, identifier: "Three", alias: "D'Anna Biers", model: "Humanoid" }, { id: 5, identifier: "Four", alias: "Simon O''Neill", model: "Humanoid" }, { id: 6, identifier: "Five", alias: "Aaron Doral", model: "Humanoid" }, { id: 7, identifier: "Six", alias: "Natalie Faust", model: "Humanoid" }, { id: 8, identifier: "Seven", alias: "Sharon Valerii", model: "Humanoid" }, { id: 9, identifier: "Eight", alias: "Samuel Anders", model: "Humanoid" }, { id: 10, identifier: "Nine", alias: "Tory Foster", model: "Humanoid" }, { id: 11, identifier: "Ten", alias: "Ellen Tigh", model: "Humanoid" }, { id: 12, identifier: "Eleven", alias: "Saul Tigh", model: "Humanoid" }, { id: 13, identifier: "Twelve", alias: "Galen Tyrol", model: "Humanoid" }, ] module.exports = cylons;

Next, create a new file in /api/v1 named cylons.js to handle the route and GET endpoint. Add the following code:

const express = require('express'); const router = express.Router(); const cylonData = require('../../data/cylons.js'); router.route('/').get(getRouteHandler); function getRouteHandler(req, res) { res.status(200).json({message: cylonData}); } module.exports = router;

Finally, update /api/v1/index.js to use the new route:

const express = require('express'); const router = express.Router(); router.use('/status', require('./status')); // New route: router.use('/cylons', require('./cylons')); module.exports = router;

Now your folder structure should look like this:

Build an API with Node - file structure after adding another route

Colonies Route

Let’s create a GET request to return a list of colonies. Here’s the data file to paste as colonies.js inside the data folder:

const colonies = [ { id: 1, name: "Aerilon", ancientName: "Aries", starSystem: "Helios Delta" }, { id: 2, name: "Aquaria", ancientName: "Aquarion", starSystem: "Helios Delta" }, { id: 3, name: "Canceron", ancientName: "Cancer", starSystem: "Helios Delta" }, { id: 4, name: "Caprica", ancientName: "Capricorn", starSystem: "Helios Alpha" }, { id: 5, name: "Gemenon", ancientName: "Gemini", starSystem: "Helios Alpha" }, { id: 6, name: "Leonis", ancientName: "Leo", starSystem: "Helios Beta" }, { id: 7, name: "Libran", ancientName: "Libra", starSystem: "Helios Gamma" }, { id: 8, name: "Picon", ancientName: "Pisces", starSystem: "Helios Alpha" }, { id: 9, name: "Sagittaron", ancientName: "Sagittarius", starSystem: "Helios Gamma" }, { id: 10, name: "Scorpia", ancientName: "Scorpio", starSystem: "Helios Gamma" }, { id: 11, name: "Tauron", ancientName: "Taurus", starSystem: "Helios Alpha" }, { id: 12, name: "Virgon", ancientName: "Virgo", starSystem: "Helios Beta" }, ] module.exports = colonies;

As before, we need to add code to handle the route. Create a new file in /api/v1 named colonies.js with this code:

const express = require('express'); const router = express.Router(); const colonyData = require('../../data/colonies.js'); router.route('/').get(getRouteHandler); function getRouteHandler(req, res) { res.status(200).json({message: colonyData}); } module.exports = router;

Lastly, add the new route to /api/v1/index.js:

const express = require('express'); const router = express.Router(); router.use('/status', require('./status')); router.use('/cylons', require('./cylons')); router.use('/colonies', require('./colonies')); module.exports = router;

Save your changes. Next, we’ll test your new API.

Testing

In your console execute npm start. You should see a message indicating that your API is running. Verify this by navigating to http://localhost:3000/api/v1/status. Since we didn’t change that route, you should see {"message":"OK"}.

Test the Cylons route next by navigating to http://localhost:3000/api/v1/cylons.

Build an API with Node - Results of the "cylons" route in the browser

It works. Awesome! But it’s kind of ugly. You can prettify JSON in your browser by adding the JSONView extension, assuming you’re using a Chromium-based browser like Brave or Chrome.

Build an API with Node - Results of the "cylons" route in the browser after prettifying the JSON

Much better!

Navigate to http://localhost:3000/api/v1/colonies to test the colonies route. You should see something like this:

Build an API with Node - Results of the "colonies" route in the browser

Nice!

Wrapping Up

Congratulations, you’ve just built a nice little API with Node. You could certainly deploy this as-is, but why stop now? Here are a few suggestions for next steps:

  • Add automated testing
  • Dockerize the application
  • Move the colony and Cylon data to Postgres
  • Build a CI/CD workflow

I’ll cover these and other topics in future articles.

What kind of API will you build?

Related Articles

0 0 vote
Article Rating
Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x