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.
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
npm
will create a simple package.json
file for you.
Next, run npm install express
:
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();
Code language: JavaScript (javascript)
Save your changes. In your console, type npm start
and press <ENTER>. If all goes well, you should see something like this:
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');
Code language: JavaScript (javascript)
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);
});
}
Code language: JavaScript (javascript)
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:
- Create an API folder to store individual routes
- 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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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;
};
Code language: JavaScript (javascript)
This file will create the app and direct requests to the various files in the api
folder.
Your file structure should look like this:
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();
Code language: JavaScript (javascript)
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/42You 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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
Now your folder structure should look like this:
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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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
.

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.
Much better!
Navigate to http://localhost:3000/api/v1/colonies
to test the colonies route. You should see something like this:
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?