Learn how to build a static API with Node.js. We'll write a node build script that converts local YAML files into a set of structured JSON output files.
This is one of several tutorials on how to build a static API. For links to the other tutorials, and for some background on this tutorial, see the introduction article.
If you'd like further background on what a static API is and why we're going through this exercise, check out Let's Talk about Static APIs.
This tutorial is going to walk through how you can build a static API without a static site generator, using a Node.js script.
Shout out! I was inspired to go back to the basics for this particular tutorial (i.e. no static site generator) by an article I read from Eduardo Bouças. The tutorial you're currently reading aims to provide only a glimpse of what you can do with Node-built static APIs. If you find it valuable, I'd suggest checking out Eduardo's article, too, as it goes into much more detail.
Okay, let's get to it!
Since we're using a plain old node script here, there's no fancy command for getting everything setup. So let's begin by creating a directory for our project:
$ mkdir project-name
$ cd project-name
package.json
Next, create a package.json
file. You could do this manually, or with an interactive process:
$ npm init
You don't have to fill anything out when going through the init
prompts. If you hit return
through the steps, Yarn will fill in sensible defaults.
After completing that step, take a look at the package.json
file. You should see the values you filled in (or the defaults).
We're going to use three libraries (plus a couple built-in Node libraries) for our build script. Install them:
$ npm install js-yaml glob http-server
.gitignore
Now we have a ton of files in node_modules
. Assuming you're using Git, add a .gitignore
file. (Note: We're adding the build
directory here at this time, but won't use that until a little later.)
.gitignore
# Installed packages
node_modules/
# Build output
build/
It's time to add our source data. These files come from the introductory article.
data/earworms/2020-03-29.yml
---
id: 1
date: 2020-03-29
title: Perfect Illusion
artist: Lady Gaga
spotify_url: https://open.spotify.com/track/56ZrTFkANjeAMiS14njg4E?si=oaaJCMbiTw2NqYK-L7CSEQ
data/earworms/2020-03-30.yml
---
id: 2
date: 2020-03-30
title: Into the Unknown
artist: Idina Menzel
spotify_url: https://open.spotify.com/track/3Z0oQ8r78OUaHvGPiDBR3W?si=__mISyOgTCy0nzyoumBiUg
data/earworms/2020-03-31.yml
---
id: 3
date: 2020-03-31
title: Wait for It
artist: Leslie Odom Jr.
spotify_url: https://open.spotify.com/track/7EqpEBPOohgk7NnKvBGFWo?si=eceqQWGATkO1HJ7n-gKOEQ
First, let's get setup. We're going to add two scripts to our package.json
file. We do this to abstract the build
and serve
commands so that all you have to worry about is running npm run build
or npm run serve
, which are easier to remember (and can be consistent from project to project).
package.json
{
// ...
"scripts": {
"build": "node bin/build.js",
"serve": "http-server build -p 8000"
}
}
The build
command will run our build
script, which we haven't created yet. And the serve
command will run a web server in the build
directory, which also doesn't exist yet. So let's make that happen.
Here's the full build script, with comments so you can follow what's happening. There's not a ton to it, really. It runs through our data files and parses them. Then it prepares our build directories and writes the parsed data as JSON files in the appropriate location.
I like to put scripts in a bin
directory so they are tucked away from the rest of the code. You're welcome to put this script anywhere you'd like, but will need to adjust the build
script in your package.json
file to point to the correct location.
bin/build.js
const fs = require("fs");
const glob = require("glob");
const path = require("path");
const yaml = require("js-yaml");
// ---------------------------------------- | Parse Data
// Object to store parsed data.
const data = [];
// Get filenames of all the YAML files in the data/earworms directory.
const dataFiles = glob.sync(path.join(__dirname, "../data/earworms/*.yml"));
// Loop through the files, read each one, parse it, and store it in the data
// object.
for (const file of dataFiles) {
const content = fs.readFileSync(file, { encoding: "utf-8" });
data.push(yaml.load(content));
}
// ---------------------------------------- | Build Directories
// Create main build directory if it doesn't exist.
const buildDir = path.join(__dirname, "../build");
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir);
// Create the directory to house the individual records, if it doesn't exist.
const indivDir = path.join(__dirname, "../build/earworms");
if (!fs.existsSync(indivDir)) fs.mkdirSync(indivDir);
// ---------------------------------------- | Index Page
// Path to the index page in the build dir.
const indexPath = path.join(buildDir, "earworms.json");
// Data for the index page.
const indexData = JSON.stringify({
results: data,
meta: { count: data.length },
});
// Write the file.
fs.writeFileSync(indexPath, indexData);
// ---------------------------------------- | Individual Pages
// Loop through the individual data records.
for (const result of data) {
// Path to the individual file.
const indivPath = path.join(indivDir, `${result.id}.json`);
// Data for the individual file.
const indivData = JSON.stringify({ result: data, meta: {} });
// Write the individual file.
fs.writeFileSync(indivPath, indivData);
}
You'll notice that in the package.json
we have the build
script running node bin/build.js
. If you want to run it directly, you can make the file executable. On Unix and Unix-like systems, you can use the chmod
command to achieve this:
$ chmod +x bin/build.js
Then you'll want to add a hashbang to the top of your build script file:
bin/build.js
#!/usr/bin/env node
# ...
Once that's in place, you can run the command directly:
$ ./bin/build.js
There isn't a ton of benefit from that in this case because we're abstracting the command in a package.json
script anyways.
Okay, now you can run the script:
$ npm run build
If everything went successfully, you should now see a build
directory with the earworms.json
file, along with the individual files.
And you can check that everything is looking good by running the server in your build directory and checking the output. To start the server, run the serve
command:
$ npm run serve
And then navigate to the appropriate place in your browser. Your index page is at localhost:8000/earworms.json and the individual pages are nested under earworms
, e.g. localhost:8000/earworms/1.json.
As you can see, there's a lot of power here without doing a ton of work. This just gives you a taste of it.
If this was something I would eventually take into production, I'd likely do a bit more with it first. Here are some ideas:
Processing ## ear worms ...
and then Done.
.Following those steps, I'd feel comfortable enough to use the script to build and deploy the API, via a service like Netlify or Vercel.
But if you wanted to take it a step further, here are a couple other ideas for adjustments you could make to the code:
And there's so many more places you could go from there. This is only a start.
So, what are you waiting for? Get building!