Quantcast
Channel: Commented Out » javascript
Viewing all articles
Browse latest Browse all 7

Creating a simple REST API with Vatican.js

$
0
0

A couple of weeks ago, I released to the inter-world my take at what a microframework for creating API’s should look like. I called it Vatican.js.
I got some interesting responses, but mostly, because of the name, so I thought I’d write a little post explaning how to go about creating a REST API from scratch using Vatican.

The API

For the purpose of this post, I’ll create a very simple API that will deal with soccer players (the worldcup started yesterday, deal with it).
It’ll all be JSON based, so everything will be structured using this format.
Having that in mind, here are our resources:

Players

We’ll be able to add, edit, list and delete players. These are their attributes:

  • id: A simple identifier for the resource
  • name: The name of the player
  • team_id: The id of the team that player belongs to
  • goals: Number of goals scored during the WorldCup

Teams

Very straight forward, teams will be a collection of players, we’ll be able to create new teams, add existing players to each one and get the full list of teams and details on each one.
Here are the attributes:

  • id: A simple identifier for the team
  • name: The name of the team
  • players: The collection of players resources

That’s it, nothing fancy, now lets get down to the coding bit…

Starting out

Assuming Vatican.js is already installed globally, we’ll start by creating the project using the cli tool:

$> vatican new worldcup-api

With that, we have the following structure:

/worldcup-api
|
|-> vatican-conf.json
|-> /handlers

The vatican-conf.json file already has the defaults values for port and handler’s folder, so it’ll look something like this:

  {
    "port": 8753,
    "handlers": "/handlers"
  }

With this in mind, lets create the file that will start up our api: index.js.
The index.js file, should initially look like this:

var vatican = require("../vatican");

var app = new vatican();
app.start();

That’s it! 3 lines of code and our API is up… ofcourse, we don’t have anything to do with it, but that comes later…

Adding the resource handlers

So, lets give this API something to do, shall we? I said we were going to handle two resources: players and teams, so lets add their handlers using the cli tool (from within the project’s folder):

$> vatican g players addPlayer updatePlayer delPlayer listPlayers
$> vatican g teams addPlayer addTeam listTeams showTeam

That’s that, two handler files have been created inside our /handlers folder, lets look at them:

players.js

module.exports = Players;
function Players() {}
@endpoint (url: /players method: ??)
Players.prototype.addPlayer = function(req, res) {}

@endpoint (url: /players method: ??)
Players.prototype.updatePlayer = function(req, res) {}

@endpoint (url: /players method: ??)
Players.prototype.delPlayer = function(req, res) {}

@endpoint (url: /players method: ??)
Players.prototype.listPlayers = function(req, res) {}

teams.js

module.exports = Teams;
function Teams() {}
@endpoint (url: /teams method: ??)
Teams.prototype.addPlayer = function(req, res) {}

@endpoint (url: /teams method: ??)
Teams.prototype.addTeam = function(req, res) {}

@endpoint (url: /teams method: ??)
Teams.prototype.listTeams = function(req, res) {}

@endpoint (url: /teams method: ??)
Teams.prototype.showTeam = function(req, res) {}

So the files are there, but all the urls for each endpoint look the same, and their method (the HTTP method) is not set…, so lets sort that out:

players.js

module.exports = Players;
function Players() {}
@endpoint (url: /players method: post) 
Players.prototype.addPlayer = function(req, res) {}

@endpoint (url: /players/:id method: put)
Players.prototype.updatePlayer = function(req, res) {}

@endpoint (url: /players/:id method: delete)
Players.prototype.delPlayer = function(req, res) {}

@endpoint (url: /players method: get)
Players.prototype.listPlayers = function(req, res) {}

teams.js

module.exports = Teams;
function Teams() {}
@endpoint (url: /teams/:id/players method: put) 
Teams.prototype.addPlayer = function(req, res) {}

@endpoint (url: /teams method: post)
Teams.prototype.addTeam = function(req, res) {}

@endpoint (url: /teams method: get)
Teams.prototype.listTeams = function(req, res) {}

@endpoint (url: /teams/:id method: get)
Teams.prototype.showTeam = function(req, res) {}

That looks much better, doesn’t it? Now the API still won’t do much, because the methods are empty, but if we do this:
$> vatican list
We’ll get the following output:

List of routes found:
[POST] /players -> ./handlers/players.js::addPlayer
[PUT] /players/:id -> ./handlers/players.js::updatePlayer
[DELETE] /players/:id -> ./handlers/players.js::delPlayer
[GET] /players -> ./handlers/players.js::listPlayers
[PUT] /teams/:id/players -> ./handlers/teams.js::addPlayer
[POST] /teams -> ./handlers/teams.js::addTeam
[GET] /teams -> ./handlers/teams.js::listTeams
[GET] /teams/:id -> ./handlers/teams.js::showTeam

Adding the logic

For simplicity purposes, I’ll keep the logic simple, saving everything in memory.
First, lets create a very basic storage system:

lib/db.js


var players= [];
var teams = [];

module.exports = {
    addPlayer: function(p) {
        players.push(p);
        if(p.team_id) {
            teams.forEach(function(t) {
                if(p.team_id == t.id) t.players.push(p);
            })
        }
    },
    updatePlayer: function(id, p) {
        players.forEach(function(player, idx) {
            if(player.id == id) players[idx] = p;
        });
    },
    deletePlayer: function(id) {
        players.forEach(function(player, idx) {
            if(player.id == id) delete players[idx];
        });
    },
    getPlayers: function() { return players; },
    
    addPlayerToTeam: function(tid, pid) {
        teams.forEach(function(t) {
            if(tid == t.id) {
                players.forEach(function(p) {
                    if(pid == p.id)
                        t.players.push(p);
                })
            }
        })
    },
    addTeam: function(t) { teams.push(t); },
    getTeams: function() { return teams; },
    getTeam: function(tid) { 
        var ret = null;
        teams.forEach(function(t) {
         if(tid == t.id) ret = t;
        })
        return ret;
    }
}

And now, lets update our handlers, to use the new storage system:

teams.js

var store = require("../lib/db");

module.exports = Teams;
function Teams() {}

@endpoint (url: /teams method: post)
Teams.prototype.addTeam = function(req, res) {
    store.addTeam(req.params.body)
    res.send(req.params.body);
}

@endpoint (url: /teams method: get)
Teams.prototype.listTeams = function(req, res) {
    res.send(store.getTeams());
}

@endpoint (url: /teams/:id method: get)
Teams.prototype.showTeam = function(req, res) {
    var tid = req.params.url.id;
    res.send(store.getTeam(tid));
}

@endpoint (url: /teams/:id/players/:pid method: put) 
Teams.prototype.addPlayer = function(req, res) {
    var tid = req.params.url.id;
    var pid = req.params.url.pid;
    store.addPlayerToTeam(tid, pid);
    res.send(store.getTeam(tid));
}

players.js

var store = require("../lib/db");

module.exports = Players;
function Players() {}
@endpoint (url: /players method: post) 
Players.prototype.addPlayer = function(req, res) {
    store.addPlayer(req.params.body);
    res.send(req.params.body);
}

@endpoint (url: /players/:id method: put)
Players.prototype.updatePlayer = function(req, res) {
    var pid = req.params.url.id;
    store.updatePlayer(pid, req.params.body);
    res.send(req.params.body);
}

@endpoint (url: /players/:id method: delete)
Players.prototype.delPlayer = function(req, res) {
    var pid = req.params.url.id;
    store.deletePlayer(pid);
    res.send(store.getPlayers()); 
}

@endpoint (url: /players method: get)
Players.prototype.listPlayers = function(req, res) {
    res.send(store.getPlayers()); 
}

Now, if you happen to try out the code above, only the add and list methods will work as expected, the rest of them (the ones that lookup a resource by id ) won’t. Why? Because the jsons we’re sending on our requests, are not parsed by Vatican.js, so req.params.body actually contains a string representation of our json, instead of the actual object.

To fix that, we could parse it on every handler method, or use a preprocessor, like this:

index.js

var vat = require("vatican");

var app = new vat();

app.preprocess(function(req, res, next) {
    if(typeof req.params.body == "string") {
        req.params.body = JSON.parse(req.params.body);
    }
    next();
})
app.start();

Adding the correct headers

To show one more feature of Vatican.js, lets think about the response, we’re rendering a json on all of the API’s responses, but we’re not sending the right content type. To solve that, we could simply add one line of code, before every res.send. But that’s not fun, is it?

Enter the post processor

We can add a post processor, that will let us modify the response, even after the res.send line. Cool isn’t it?

index.js

var vat = require("vatican");

var app = new vat();

app.preprocess(function(req, res, next) {
    if(typeof req.params.body == "string") {
        req.params.body = JSON.parse(req.params.body);
    }
    next();
})

app.postprocess(function(req, res, next) {
	res.setHeader(["content-type", "application/json"]);
	next();
})
app.start();

Error handling

The pre and post processors also allow us to define error handlers that can be re-used by the entire project. To setup an error handler, you only need to pass one extra parameter to the processor: the error.
So, lets first setup a generic error handler on our index.js file:

index.js

var vat = require("vatican");

var app = new vat();

app.preprocess(function(req, res, next) {
    if(typeof req.params.body == "string") {
        req.params.body = JSON.parse(req.params.body);
    }
    next();
})
//It'll grab the error message, create an error object and set the statusCode to 500
app.preprocess(function(err, req, res, next) {
	var errorObj = {
		error: true,
		msg: err
	};
	res.statusCode = 500;
	res.send(errorObj);
})

app.postprocess(function(req, res, next) {
	res.setHeader(["content-type", "application/json"]);
	next();
})
app.start();

Now lets add an error condition somewhere on our code:
players.js

//...
@endpoint (url: /players/:id method: delete)
Players.prototype.delPlayer = function(req, res, next) {
    var pid = req.params.url.id;
    if(+pid < 0) {
    	next("The player id must be > 0");
    } else {
	    store.deletePlayer(pid);
	    res.send(store.getPlayers()); 
	}
}
//..

Now, if we were to try to delete player id -2, then we would get an error message on our response, instead of the expected behavior.

Final words

Thanks for reading to the end, I hope you enjoyed it and decided to give Vatican.js a try. I’m constantly looking for feedback, so please, let me know if you think there are any areas where the framekwork can improve on.
In case you do want to read more, pelase visit Vatican’s page: www.vaticanjs.info


Viewing all articles
Browse latest Browse all 7

Latest Images

Trending Articles





Latest Images