rebound-api
v1.0.4
Published
An Express middleware for elegant API creation
Downloads
4
Readme
Hey! This module has been spun off into its own project from the Rebound Seed Project and can be used as a standalone library for API creation. Feel free to use this with or without the rest of Rebound, though we definately recommend checking it out!
Then, in your app.js file:
// Get our dependancies
var express = require('express');
var api = require('rebound-api');
// Init Core
var app = express();
app.set('port', PORT);
/*******************************************
Additional Middleware Go Here
*******************************************/
// Automatically discover API endpoints in `/api` directory. Must be last middleware.
app.use(api(express));
// Start Server
http.createServer(app).listen(app.get('port'), function(){
console.log(('Express server listening on port ' + app.get('port')));
});
Its quite simple really – the Rebound API middleware checks req.xhr
:
If the request is an AJAX request, it will attempt to roue to an API endpoint defined in your project's /api
directory and send a JSON response back to the client.
If the request is not an AJAX request, it will respond using a file that you specify (defaults to /index.html
).
Important: This middleware will catch all requests – both AJAX and otherwise – so it must be the last middleware in your express server.
1) API Discovery
When starting, the Rebound API middleware will look for a directory called /api
at the root of your project. This directory contains all the files that will define your api (how that works is described below), the paths of which define your public facing API paths.
The project on the left in the below image, will show its corrosponding output when you start your server. This example will be referred to throughout this section:
API File Paths:
The files paths in your /api
directory are the express routes you would normally write at the bottom of your app.js file to handle requests. Here is the express router documentaiton if you need to brush up on how to write routes.
The file and directory names may be any valid string or string pattern used to describe an Express route. For example: /user/:uid?.js
, as is shown in the above example, defines a route user
that can accept an optional uid
(User ID) parameter.
The file name index.js
is special. Files named index.js
will act as the root route for their parent directory. The donate
directory in the above example shows this well. the directory structure:
api
|--- donate
|--- history.js
|--- index.js
Defines two routes: /donate/history
and /donate
. An equivelent, but far less convenient, structure would be:
api
|---donate
| |--- history.js
|
|---donate.js
API Path Specificity:
No more manually managing your route ordering! Your routes are automagically registered with express in order from most to least specific. For instance, above, the user
paths are loaded in order from most to least specific: /user/password/recovery
> /user/login
> /user/:uid?
.
API Errors
The Rebound API middleware will display the paths discovered in your console for your debugging pleasure. If there is an error in one of your API files, it will not kill your server. Instead, it will print a nice big, red error for that route along with the error and line number that caused it. Convenient!
2) Writing API Files
We will be using jSend, a simple (and personal favorite) JSON format for sending data back and forth between the server and frontend, in these examples. Feel free to use whatever specification you like best!
The files in your /api
folder export the different http methods implemented for this path. The methods implemented for a particular path are printed out next to the registered path in the console, as shown in section 1.
A simple API file may look like this:
// This file implements the `GET` HTTP method, which returns a JSON blob.
// This JSON is sent back to the client and the response is closed.
exports.GET = function(req, res){
return {
status: 'success',
data: {
firstName: 'Luke',
lastName: 'Skywalker'
}
};
For you lazy people out there – a tl;dr:
- These HTTP method implementations are middleware.
- Different HTTP methods are named exports from your API file. Current valid methods are
ALL
,GET
,POST
,UPDATE
andDELETE
. - Like any other middleware, they are passed the
req
andres
objects. - These API definitions do not accept a
next
callback – they are always the last middleware before a response. - These middleware should always return either
JSON
or aPromise
. - The response value will be sent back to the client.
- If the response is a
Promise
, the Rebound API will wait for it to resolve and send its value. - If the response JSON has the property
code
, it will be use as the HTTP status code of the response. - If an error occurs in your API call, it will be:
- Gracefully caught
- Logged in the console
- And
500
response will be sent back to the client - If no route is found that matches the request, a
400
response is sent back to the client
The full explaination:
An API file that only exports a single function will default to the GET
http method:
// Same as the example above
module.exports = function(req, res){
return {
status: 'success',
data: {
firstName: 'Luke',
lastName: 'Skywalker'
}
};
};
An API file that may export multiple HTTP method names:
module.GET = function(req, res){
// Make a database call or something here
return {
status: 'success',
data: {
firstName: 'Luke',
lastName: 'Skywalker'
}
};
};
module.POST = function(req, res){
// Update your database or something here
return { status: 'success' };
};
An API method implementation may return a Promise. Rebound API will wait for the promise to resolve or reject and sent its resolved or rejected value back to the client. Great for asynchronous database calls:
var Promise = require("bluebird");
module.GET = function(req, res){
// Make an async database call or something here
return new Promise(function(resolve, reject){
resolve({
status: 'success',
data: {
firstName: 'Luke',
lastName: 'Skywalker'
}
});
});
};
module.POST = function(req, res){
// Do some async validation or something here
return new Promise(function(resolve, reject){
reject({
status: 'error',
code: '403', // If the response has a property `code`, it will be used as the http response code.
message: 'You are not authorized to post to this endpoint!'
});
});
};
3) Calling APIs Server Side
The Rebound API middleware puts an api
property on on the res.locals
object at the begining of every new request and is accessable to every middleware in your express app. It exposes get
, post
, put
and delete
methods which each take a url and optional data object. This allows you to consume your API calls server side to build more powerful services, as well as client side.
A server side call looks like this:
res.locals.api.get('/user/123')
.then(function(result){
// You have access to the API result here
});
An internal API call will always return a Promise, regardless of if the API function returns a Promise itself, or just a JSON blob.
You are able to pass internal API calls an optional JSON object as a second paramater. This object will augment the req.body
object on the original request object for the lifetime of the internal API call.
Our API file
/api/user/:uid.js
exports.POST = function(req, res){
// req.body == { firstName: 'Ash', numPkmn: 6 }
return { status: 'success', data: { yell: 'Gotta Catch 'Em All!' }}
}
Middleware posting to
/user/:uid
function(req, res){
// req.body = { numPkmn: 6 }
res.locals.api.post('/user/123', { firstName: 'Ash' })
.then(function(result){
// You have access to the API result here
// result.data == { yell: 'Gotta Catch 'Em All!' }
// req.body == { numPkmn: 6 }
});
}
The fact that internal APIs always return a promise allows us to do some creative things when drafting other API calls, consuming existing APIs to create new ones.
An API file
/api/profile/:uid.js
. This endpoint returns an entire profile object. So much info!
exports.GET = function(req, res){
return {
status: 'success',
data: {
firstName: 'Ash',
lastName: 'Ketchum',
numPkmn: 151,
hometown: 'Pallet Town'
}
}
}
An API file
/api/miniprofile/:uid.js
. This miniprofile API endpoint will only return thefirstName
andlastName
properties of the full profile.
exports.GET = function(req, res){
res.locals.api.get('/user/' + req.params.uid)
.then(function(result){
result.data = {
firstName: result.data.firstName,
lastName: result.data.lastName
};
return result;
});
}