API

Make an Api

APIs

Many people who are new to programming have probably fetched data from an API as part of a project. If this describes you, you have mostly likely looked up free APIs that contain data for things such as weather information, or have made use of the APIs from such websites as facebook, twitter or instagram and incorporated them in you own app. In this walkthrough you will be shown how to build your own API and incorporate it into your app.

API stands for Application Programming Interface and acts as the communication between two applications. Just as you have at one point fetched data from someone else’s API and put it in your app, you can make an API yourself as a middleman between your frontend and any data it needs to function or supply to the user.

Database-less

Many of the walkthroughs you will see online about making your first API will involve using a database such as Mongodb or mySQL. This is necessary for bigger projects but in this case we are only going over the basics and a simple JSON file will suffice.

CRUD

The API we will be making in this tutorial will have CRUD functionality. CRUD stands for Create Read Update and Delete. This means that your API should be able to add new records, read existing ones, update a specific entry and delete entries.

Tools

This walkthrough will make use of Postman as a way to check our work. You will also need to have express, cors, and body-parser saved in your package.json.

$ npm install express
$ npm install body-parser
$ npm install cors

Getting Started

Lets say we organize fun activities for our town and we want to make a page that displays upcoming events. Each event will have a date, a title and a description. This is not something we want to change the our display page every time an event passes or is added or changed. It would be much easier to store this data, call it with our API, and then map it to our HTML.

Since we wont be using a database the first thing we are going to do is make a JSON file to store the data we already have. Make sure that your file is saved with the JSON extension and that your file is properly formatted. Below is the JSON file I will be working with.

[
  {
    "id":1,
    "title":"Apple Picking Day",
    "data":"6/13",
    "description":"A fun day of picking apples off the ground!  What?!  We don't have
    an orchard, what do you want from us?!",
  },
  {
    "id":2,
    "title":"Petting Zoo",
    "data":"7/20",
    "description":"Petting zoo and free pony rides, definitely not two guys in a horse suite.",
  },
  {
    "id":3,
    "title":"Kale Festival",
    "data":"8/3",
    "description":"Yeah... we know... it's not ideal.",
  },
  {
    "id":4,
    "title":"Progressive Post Pop Folk Festival",
    "data":"9/2",
    "description":"It's music... technically!",
  }
]

This api will primarily be based off of two files, app.js which will serve as the main entry point of our app and routes.js which will contain all of the routes.

Below is my app.js and routes.js files in their entirety, to learn about the break down of these files keep reading.

app.js

const express = require('express');
const bodyParser = require('body-parser');
const routes = require('./routes.js');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
routes(app);
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, contentType, Content-Type, Accept, Authorization");
  next();
});
app.listen(4000, (err) => {
  if (err) {
    console.log(err)
  } else {
    console.log('Port is 4000');
  }
});

routes.js

const data = require('./data.json');
const fs = require('fs');
let count = require('./count.json');
let len = data.length
const ele = ['title', 'date', 'description'];
const newEntry = (body, entry) => {
  if (entry === '') {
    entry = {
      id: count,
      title: '',
      date: '',
      description: ''
    };
  }
  for (let i = 0; i < ele.length; i++) {
    if (body[ele[i]] !== undefined) {
      entry[ele[i]] = body[ele[i]];
    }
  }
  return (entry);
};
const appRouter = (app) => {
  app.post('/', (req, res) => {
    count++;
    fs.writeFile('./count.json', count, () => {console.log('Count file updated.')})
    data.push(newEntry(req.body, ''));
    len = data.length;
    fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Entry added.')});
    res.send(data);
  });
  app.get('/', (req, res) => {
    res.send(data);
  });
  app.put('/', (req, res) => {
    if (req.body.id === undefined || Number.isInteger(Number(req.body.id)) === false) {
      console.log('ID required to change an entry.');
    }
    else {
      for (let j = 0; j < len; j++) {
        if (data[j].id === parseInt(req.body.id)) {
          data[j] = newEntry(req.body, data[j]);
        }
      }
      fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Data file updated.')});
    }
    res.send(data);
  });
  app.delete('/', (req, res) => {
  if (req.body.id === undefined || Number.isInteger(Number(req.body.id)) === false) {
    console.log('ID required to delete an entry.');
  }
  else {
    for (let i = 0; i < len; i++) {
      if (data[i].id === parseInt(req.body.id)) {
        data.splice(i, 1);
        len = data.length;
      }
    }
    fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Entry deleted.')});
  }
  res.send(data);
});
};
module.exports = appRouter;

To run your app you will have to enter “npm start” in your console after you add “start”: “node app.js” to scripts in package.json. While I was doing the work for this walkthrough I had to stop and restart the app in the console whenever a change was made, so if you not seeing changes you just added you may need to restart the app.

Getting GET (Read)

The first thing we will do is create our routes.js file so that when we set up our app there is something being provided to it. First require your JSON file that contains all of your data. Next we will create our appRouter, this is where all of our routes will be stored. For now the only thing appRouter will contain is a GET command. GET is the route that is for viewing the data only, this is a good starting point because this will be the easiest one to write callback functions for and allows us to see our results. The rest of our CRUD functionality will be made later.

Finally don’t forget to export appRouter so that it can be used by app.js.

const data = require('./data.json');
const appRouter = (app) => {
  app.get('/', (req, res) => {
    res.send(data);
  });
};
module.exports = appRouter;

The first thing we will do in our app.js file is require express. Express is a web application framework for node.js and is what we will be building our app with. Along with express you will also want to import body-parser which will be used as a middleware function to parse requests of different types of payloads. Finally make sure to include routes.js

After your imports the first thing you should do is create your express app. After it is created, use app.use to include the body-parser middleware functions. Use is an express method that mounts a middleware function or functions. In this case we are using middleware functions for both JSON and urlencoded payloads.

Now we will use routes with the express app we just created.

Finally we will tell our app to listen at localhost port 4000.

const express = require('express');
const bodyParser = require('body-parser');
const routes = require('./routes.js');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
routes(app);
app.listen(4000, (err) => {
  if (err) {
    console.log(err)
  } else {
    console.log('Port is 4000');
  }
});

Now lets check and make sure things are running as they should. Go to localhost:4000, you should see your data similarly to the picture below.

While we are checking we should probably take a look at our data through Postman. Although it isn’t quite necessary to use Postman right now, since the GET command is something we can witness through localhost, it will come in handy when we want to test our POST, PUT and DELETE commands. In the text box where the url is required put localhost:4000 and make sure that the selected command in the drop down menu to the left is set to GET. You should get the data below, this time formatted in a slightly more readable way.

POST It (Create)

The next command that we will add to our route is the POST command, this is create, the C in CRUD. This will be the command that allows us to add new entries to our data. At this point we will be making changes to our data.json and another JSON file we will be making shortly so make sure you have require(‘fs’) near the top of your file.

To add another route, go into routes.js and in appRouter, add an app.post option. In this, you will write what you want to happen whenever a client uses the POST command. In any command you will be given a request, in this case labeled as req. Req.body will contain all of the information the client gave you. For POSTs this information would be the pieces of data and their values. It is possible to send this client supplied entry straight to our data.json file but I want to make sure each entry is formatted the same way so that the body can’t add new titles, and that if it doesn’t have values for all the data points, there would be a blank space for it. Because of this I included a function called newEntry. newEntry will take in both the contents of the client’s request and the preexisting entry if there is one. It will then cycle through and only accept changes to data points that are one of the ones established earlier: title, date and description.

const data = require('./data.json');
const fs = require('fs');
let count = require('./count.json');
let len = data.length
const points = ['firstName', 'lastName', 'age', 'email', 'job', 'college', 'avatar', 'bio'];
const newEntry = (body, entry) => {
  if (entry === '') {
    entry = {
      id: count,
      title: '',
      date: '',
      description: ''
    };
  }
  for (let i = 0; i < points.length; i++) {
    if (body[ele[i]] !== undefined) {
      entry[ele[i]] = body[ele[i]];
    }
  }
  return (entry);
};

In this case there is no preexisting entry because we are writing a POST command, so I call newEntry with entry set to empty parenthese. I have a conditional statement at the start of the function to create an object with all of the desired data points set to empty except for ID.

I want to make sure that each entry has a unique id, this is good practice and will also make changing and deleting entries easier. I want to make a counter that is saved and only increases in value, this way we wont encounter the same number twice when generating an id. To do this I added another JSON file that only contains one number. This file is required at the beginning of routes.js and it’s contents are given to the variable count. In POST, count is increased and it’s new value is saved to the count.json file.

After newEntry creates an entry that is empty (aside from the new id), a for loop cycles through all of the values of the array points. Points is the name of all the data points that are acceptable inputs. For all data titles, it checks the request’s body and if it contains that datapoint, it’s values are given to the empty entry. The function then returns entry.

app.post('/', (req, res) => {
  count++;
  fs.writeFile('./count.json', count, () => {console.log('Count file updated.')})
  data.push(newEntry(req.body, ''));
  len = data.length;
  fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Entry added.')});
  res.send(data);
});

The value of newEntry is pushed to our data and the length variable that was made at the top of the file is updated in case it is used later. Count.json and data.json are updated with writeFile so that our change to the data and count value is saved. Console.logs are added so that you know when your action was successful. The last thing in POST will be another res.send(data), this is your read response but is useful to put in all other commands so that you can see the results.

To test whether or not your post is successful go to Postman. This time instead of GET select POST in the drop down to the left. Click the body tab and give it the values you want the new entry to have. After clicking send your data should be displayed below but this time with the new event.

DELETE

This is where our unique id comes into play. Two events could have the same date or titles, maybe apple picking has really caught on, so selecting an entry by its id is the best way to go about it. Our DELETE route will take an id from the client and delete the entry that has that id.

The first thing in app.delete is a conditional statement that is dependent on whether or not the client inputted a valid id number. If anything other than an integer is given to our app, the output will be “ID required to delete an entry.” in the console.

If the input was a valid id value, a for loop will go through the length of data and check if value of id equals the numerical value of the id given in the body. It is necessary to use part in on req.body.id because it may not be a numerical value. If there is a match, splice is used to take the entry with the matching id out and the variable len which equals our data’s length, is changed. After the for loop is done the data.json file is updated. Once again we are using res.send(data) so that you can see the results.

app.delete('/', (req, res) => {
if (req.body.id === undefined || Number.isInteger(Number(req.body.id)) === false) {
  console.log('ID required to delete an entry.');
}
else {
  for (let i = 0; i < len; i++) {
    if (data[i].id === parseInt(req.body.id)) {
      data.splice(i, 1);
      len = data.length;
    }
  }
  fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Entry deleted.')});
}
res.send(data);
});

In Postman switch to DELETE and in body make sure the only contents are “id” and the id of the entry you want to delete. In this case I want to get rid of the entry we just added. No more county fairs! I heard they were having problems with their rides.

PUT (Update)

The final route in our CRUD API is PUT which will allow us to change a pre-existing entry. The contends of PUT will take a little bit from DELETE and POST. You will probably recognize the conditional statement as the same one from DELETE to make sure that the inputted id is a valid value.

If the id is valid there is another for loop that goes through the data and finds the entry with the matching id. newEntry is called again, this time we are changing an entry that already exists so the first condition is skipped over and it’s entry value is the part of our data that we are going to change. The for loop in newEntry goes through and changes the values that were given by the client and outputs entry. Like the the previous routes data.json is updated with writeFile and the data is displayed with res.send(data).

app.put('/', (req, res) => {
  if (req.body.id === undefined || Number.isInteger(Number(req.body.id)) === false) {
    console.log('ID required to change an entry.');
  }
  else {
    for (let j = 0; j < len; j++) {
      if (data[j].id === parseInt(req.body.id)) {
        data[j] = newEntry(req.body, data[j]);
      }
    }
    fs.writeFile('./data.json', JSON.stringify(data), () => {console.log('Data file updated.')});
  }
  res.send(data);
});

One more time we will check in Postman that our API is doing what we want.
Select PUT, give the body the id of the entry you want to change and the values you want to update.

CORS

Your API now has CRUD functionality! There is one more thing you will have to add to make it usable and that is CORS. CORS stands for Cross-Origin Resource Sharing and you will need it to share your API with other or even yourself.
CORS grants permission to access resources from an origin that the API itself is not on. Eventually you can use your API as the backend of an app, but in order to do this you will need your frontend to be able to access it.

Go back to app.js and require CORS and then add it to your express app. Include the following code before app.listen and you are good to go.

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, contentType, Content-Type, Accept, Authorization");
  next();
});