Building a Simple Searchable API with Express (Backend)

Express is still one of the most prominent server side frameworks for node. This little guide will show you how you can build a simple API and connect it with your frontend framework of choice.

If you’re trying to build a site that features a list of items, stores, products or similar on your front page, read on. Examples of this could be sites like producthunt.com or the Google Play Store.

This microproject will be about the following things:

  • display data, transmitted through an API
  • enable the user to filter and select categories or tags

First we’ll write the backend server, the frontend will follow in the next post.

Requirements

This post doesn’t assume much knowledge of anything really and is aimed at beginners with the slightest of command line skills. The full code will be provided at the bottom of the post.

For the best development experience, I recommend you install nodemon (or similar) with:

npm install -g nodemon

Setting up the Express API Backend

Next, we’ll need to setup our project folder/directory:

cd projects
mkdir store-locator
cd store-locator
mkdir server
cd server
npm init -y
npm install express --save
touch server.js

Alright, now we have a basic folder structer and if we enter the following into server.js:

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

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

app.listen(3000, function(){
  console.log('Example app listening on port 3000!')
});

To test if our server works out alright, we can now start the server:

nodemon server.js

Output:

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Example app listening on port 3000!

and visit http://localhost:3000 in your web browser of choice! Hint: Firefox has become awesome again πŸ˜‰

Adding Mock Data to your API

Alright, we got the basics working, but the hello world text will not get our startup far. You need to actually provide valuable content to your users if you want to become the next yellow pages or facebook or store locator for supernatural goods!

In order to satisfy our incoming users without to much of a hassle, let’s pack a couple of example data into a file located in our data folder. In the real world the data obviously would come from your database:

mkdir data
touch data/stores.js

This is the set of example-data I’m going to use for this project:

const stores = [
  {
    id: 1,
    name: 'Hollows End Grim Potions',
    imageURL: 'https://images.unsplash.com/photo-1465433360938-e02f97448763?auto=format&fit=crop&w=1267&q=60&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D',
    location: 'Hollows End',
    nsfw: true
  },
  {
    id: 2,
    name: 'Lembas Food Truck',
    imageURL: 'https://images.unsplash.com/reserve/DHHQbqc0RrWVf0uDNe5E_The%20Litte%20Cafe.jpg?auto=format&fit=crop&w=1489&q=60&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D',
    location: 'Lothlorien',
    nsfw: false
  },
  {
    id: 3,
    name: 'Galadriels Mirror of Brutal Truth',
    imageURL: 'https://images.unsplash.com/photo-1497519098947-a305f214d3bc?auto=format&fit=crop&w=1350&q=60&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D',
    location: 'Lothlorien',
    nsfw: true
  },
  {
    id: 4,
    name: 'Jabbas Light Sabers',
    imageURL: 'https://images.unsplash.com/photo-1481241857164-e8483bce922d?auto=format&fit=crop&w=1353&q=60&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D',
    location: 'Mos Eisley',
    nsfw: true
  }
];

module.exports = stores;

Alright, that will do for example data. Only really important about this are:

  • id
  • location
  • nsfw (or other flag)

The directory for the project should now look like this:

.
β”œβ”€β”€ data
β”‚   └── stores.js
β”œβ”€β”€ node_modules
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ README.md
└── server.js

Now these are a bunch of imaginary shops, but how do we actually get them out to our users?

On top of the file, add:

const stores = require('./data/stores.js');

and below the / route:

app.get('/api/stores', function(req, res){
  res.json(stores);
});

Now when you access http://localhost:3000/api/stores you should be able to see the following:

Firefox JSON pretty print

Hint: Yes, the new Firefox does pretty JSON by default.

Now we can provide all of our example data to our users, but how can we enable them to filter it?

Filtering API queries with Express

We will need to parse the queries that our users throw at us, for example with GET parameters or query parameters. Adjusting the following will do the trick if we want to filter by nsfw or not nsfw:

app.get('/api/stores', function(req, res){
  var response = [];

  if( typeof req.query.nsfw != 'undefined' ){
    response = stores.filter(function(store){
      if(store.nsfw === true){
        return store;
      }
    });
  } else {
    response = stores;
  }

  res.json(response);
});

We can test this by accessing these URLs:

That’s great, but maybe we also want a drop-down for the location, so if we would implement it just like the other if conditional we would not be able to filter for both, because we overwrite the array response. We’ll need to implement it a bit differently and also take care of de-duplication. Note that usually your database server would take care of that and we have to write extra lines because we’re using a simple array.

Let’s make use of lodash for the de-duplication to ensure unique IDs on the API response:

npm install --save lodash

and then add on top of the file near the other requires:

const _ = require('lodash');

So that we can adjust our /api/stores route like this:

app.get('/api/stores', function(req, res){
  var response = [];
  console.log(req.query)

  // this would usually adjust your database query
  if(typeof req.query.nsfw != 'undefined'){
    stores.filter(function(store){
      if(store.nsfw.toString() == req.query.nsfw){
        response.push(store);
      }
    });
  }

  // this would usually adjust your database query
  if(typeof req.query.location != 'undefined'){
    stores.filter(function(store){
      if(store.location === req.query.location){
        response.push(store);
      }
    });
  }

  // de-duplication:
  response = _.uniqBy(response, 'id');

  // in case no filtering has been applied, respond with all stores
  if(Object.keys(req.query).length === 0){
    response = stores;
  }

  res.json(response);
});

Note that we’re not taking care of responding with the full list of stores in the bottom if no keys are set on the req.query object.

Now we can also mix the search or filter by adding multiple query parameters, try accessing http://localhost:3000/api/stores?nsfw=true&location=Lothlorien, which will include all results that either are nsfw or at the location Lothlorien. I know it’s actually LothlΓ³rien btw, but that’s beyond the point of this post.

Summary

Now you’ve created a basic API that accepts query parameters to filter items by query parameters with express.js. In the next post we’re going to have a look at how to build a frontend to actually display the stores from different fantasy universes properly.

If you’re looking for a good place to host your backend, check out: Best Cheap VPS Hosting Comparison.

If you have any questions or additions, please throw me a tweet or leave a comment below!

Here is the code that should be semi copy-paste ready:


const _ = require('lodash');
const express = require('express');
const app = express();

const stores = require('./data/stores.js');

// if you have some query parameters that are not vital to the display, just list them here and check for them later
const possibleQueryVars = [
  'nsfw',
  'location'
];

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

app.get('/api/stores', function(req, res){
  var response = [];
  console.log(req.query)

  // this would usually adjust your database query
  if(typeof req.query.nsfw != 'undefined'){
    stores.filter(function(store){
      if(store.nsfw.toString() == req.query.nsfw){
        response.push(store);
      }
    });
  }

  // this would usually adjust your database query
  if(typeof req.query.location != 'undefined'){
    stores.filter(function(store){
      if(store.location === req.query.location){
        response.push(store);
      }
    });
  }

  // de-duplication:
  response = _.uniqBy(response, 'id');

  // in case no filtering has been applied, respond with all stores
  if(Object.keys(req.query).length === 0){
    response = stores;
  }

  res.json(response);
});

app.listen(3000, function(){
  console.log('Example app listening on port 3000!')
});

Leave a Reply

Your email address will not be published. Required fields are marked *