{ Introduction to building JSON APIs. }

Objectives:

By the end of this chapter, you should be able to:

  • Explain what an API is
  • Test an API using curl and postman
  • Build a simple API with Express and Mongoose

API Intro / Terms

Since we are building an API, we are going to shift our mindset a bit in terms of how our server responds to requests. Instead of rendering or redirecting, we will be responding with JSON using res.send. This means we will not have routes (also called endpoints) for rendering forms or any kind of server-side templating. This also means that for a resource like instructors, our routes are as follows:

HTTP Verb Path Description
GET /instructors Show all instructors
GET /instructors/:id Show a single instructor
POST /instructors Create an instructor
PATCH /instructors/:id Update an instructor
DELETE /instructors/:id Delete an instructor

But what about GET /instructors/:id/edit and GET /instructors/new? Remember, we are not doing any kind of rendering. Instead, our API will be listening directly for POST / PATCH and DELETE requests instead of requiring a form submission.

These requests will come in via AJAX or other servers making requests, but NOT by clicking on a link or submitting a form (unless the default event is prevented and AJAX is used).

Building an API

Since we're using Mongoose, we can easily respond with JSON just by using res.send. So, all we need to do is respond with JSON and a status code. Here are the status codes we will be using.

200 - OK (for updating / getting)
201 - Created (for creating)
204 - No Content (for deleting)
500 - Internal Server Error (if something goes wrong on our end)

Let's rebuild our pets app but without the views!

mkdir pets-app-api && cd pets-app-api
touch app.js
npm init -y
npm install --save express body-parser mongoose morgan # we don't need method-override or pug because we will not be dealing with any views/forms
mkdir routes models
touch models/{index,pet}.js
touch routes/pets.js

Let's start with our models/pet.js:

var mongoose = require("mongoose");
var petSchema = new mongoose.Schema({
    name: String
});

var Pet = mongoose.model('Pet',petSchema);

module.exports = Pet;

Next, our models/index.js:

var mongoose = require("mongoose");
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/pets-app-api');
mongoose.Promise = Promise;

module.exports.Pet = require('./pet');

Now let's move to our routes/pets.js:

var express = require("express");
var router = express.Router();
var db = require('../models') // by default, require will look for a file called index.js so we dont need ../models/index

router.get('/', function(req, res, next){
    db.Pet.find().then(function(pets){
        // no rendering or redirecting!
        res.status(200).send(pets);
    });
});

router.get('/:id', function(req, res, next){
    db.Pet.findById(req.params.id).then(function(pet){
        // no rendering or redirecting!
        res.status(200).send(pet);
    });
});

router.post('/', function(req, res, next){
    db.Pet.create(req.body).then(function(pet){
        // 201 for created instead of 200 to be more specific
        res.status(201).send(pet);
    });
});

router.patch('/:id', function(req, res, next){
    db.Pet.findByIdAndUpdate(req.params.id, req.body).then(function(pet){
        // no redirecting!
        res.status(200).send(pet);
    });
});

router.delete('/:id', function(req, res, next){
    db.Pet.findByIdAndRemove(req.params.id).then(function(pet){
        // 204 for no content instead of 200 to be more specific
        res.status(204).send('Deleted!');
    });
});

module.exports = router;

Finally, let's finish things up with by writing our app.js:

var express = require("express");
var app = express();
var morgan = require("morgan");
var bodyParser = require("body-parser");
var petsRoutes = require("./routes/pets");

app.use(morgan("tiny"));
app.use(bodyParser.json()); // we need this to parse JSON!
app.use(bodyParser.urlencoded({extended:true}));

app.use('/api/pets', petsRoutes);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.send({
      message: err.message,
      error: err
    });
  });
}

app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.send({
    message: err.message,
    error: {}
  });
});

app.listen(3000, function(){
  console.log("Server is listening on port 3000");
});

Using an API

To test our API we can use the curl command or a more visual tool like postman. Here are examples of what the curl commands you can issue to your server:

# add some pets
# make sure you set the appropriate header!
curl localhost:3000/api/pets -d '{"name": "Fido"}' -H "Content-type: application/json"
# {"__v":0,"name":"Fido","_id":"58c0479844de069cac98ccf1"}% 
curl localhost:3000/api/pets -d '{"name": "Lassie"}' -H "Content-type: application/json"
# {"__v":0,"name":"Lassie","_id":"58c047af44de069cac98ccf2"}% 

# get all pets
curl localhost:3000/api/pets
# [{"_id":"58c0479844de069cac98ccf1","name":"Fido","__v":0},{"_id":"58c047af44de069cac98ccf2","name":"Lassie","__v":0}]%

# get a single pet
curl localhost:3000/api/pets/58c0479844de069cac98ccf1
# {"_id":"58c0479844de069cac98ccf1","name":"Fido","__v":0}% 

# update a pet - we need to set the verb!
curl localhost:3000/api/pets/58c0479844de069cac98ccf1 -X PATCH -d '{"name": "Marmaduke"}' -H "Content-type: application/json"
# {"_id":"58c0479844de069cac98ccf1","name":"Fido","__v":0}% 
# note that by default we get the previous version of the pet. But it has been updated, you can verify this if you issue another GET

# delete a pet - we need to set the verb!
curl localhost:3000/api/pets/58c047af44de069cac98ccf2 -X DELETE
# no response data

# check the database again
curl localhost:3000/api/pets
# [{"_id":"58c0479844de069cac98ccf1","name":"Marmaduke","__v":0}]% 

Sample application

You can see a sample application with JSON API here.

When you're ready, move on to Authentication with JSON APIs

Continue