Skip to content

Instantly share code, notes, and snippets.

@tj
Created July 5, 2011 12:52
Show Gist options
  • Save tj/1064789 to your computer and use it in GitHub Desktop.
Save tj/1064789 to your computer and use it in GitHub Desktop.
express auto-generated rest client
var express = require('express')
, client = require('../')
, app = express.createServer();
var users = [
{ name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { name: 'manny' }
];
var repos = {
manny: ['express', 'jade', 'stylus']
, tobi: ['kue', 'cluster']
};
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.register('.html', require('ejs'));
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.param('id', function(req, res, next, id){
var user = users[id];
if (!user) return next('route');
req.user = user;
user.id = id;
next();
});
app.get('/', function(req, res, next){
res.render('index');
});
app.get('/api/users', function(req, res, next){
res.json(users);
});
app.del('/api/users', function(req, res){
users.length = 0;
res.json({ message: 'users deleted' });
});
app.get('/api/user/:id', function(req, res, next){
res.json(req.user);
});
app.get('/api/user/:id/repos', function(req, res, next){
res.json(repos[req.user.name]);
});
app.put('/api/user/:id', function(req, res, next){
users[req.user.id] = req.body;
res.json({ message: 'user updated' });
});
app.del('/api/user/:id', function(req, res, next){
users.splice(req.user.id, 1);
res.json({ message: 'user deleted' });
});
app.use('/api', client('/api', app));
app.use(function(req, res){
res.send(404);
});
console.log('// generated js:');
console.log(client.compile('/api', app));
app.listen(3000);
var client = function(exports, request){
// setup exports
exports.users = exports.users || {};
exports.user.repos = exports.user.repos || {};
/*
* GET users.
*/
exports.users = function(){
return request("GET", "/api/users");
};
/*
* GET user.
*/
exports.user = function(id){
if (null == id) throw new Error("id is required");
return request("GET", "/api/user/" + id + "");
};
/*
* GET user repos.
*/
exports.user.repos = function(id){
if (null == id) throw new Error("id is required");
return request("GET", "/api/user/" + id + "/repos");
};
/*
* DELETE users.
*/
exports.users.del = function(){
return request("DELETE", "/api/users");
};
/*
* DELETE user.
*/
exports.user.del = function(id){
if (null == id) throw new Error("id is required");
return request("DELETE", "/api/user/" + id + "");
};
/*
* PUT user.
*/
exports.user.put = function(id){
if (null == id) throw new Error("id is required");
return request("PUT", "/api/user/" + id + "");
};
};
/*!
* express-rest
* Copyright(c) 2011 TJ Holowaychuk <[email protected]>
* MIT Licensed
*/
/**
* Expose `middleware`.
*/
exports = module.exports = middleware;
/**
* Library version.
*/
exports.version = '0.0.1';
/**
* Generate the rest client.
*
* @param {String} path
* @param {HTTPServer} app
* @return {String}
* @api public
*/
function middleware(path, app) {
var js = exports.compile(path, app);
return function(req, res, next){
if (0 != '/client.js'.indexOf(req.url)) return next();
res.contentType('.js');
res.send(js);
}
}
/**
* Compile routes for `app` into a rest client.
*
* @param {String} path
* @param {HTTPServer} app
* @return {String}
* @api private
*/
exports.compile = function compile(path, app) {
var map = app.routes.routes
, keys = Object.keys(map)
, ret = {};
// filter irrelevant paths
keys.forEach(function(method){
var routes = map[method];
ret[method] = routes.filter(function(route){
return 0 == route.path.indexOf(path);
});
});
// generate route objects
keys.forEach(function(method){
var routes = map[method];
ret[method] = routes.map(function(route){
var ret = {};
ret.path = parsePath(route.path.replace(path, ''));
ret.prop = createProp(ret.path);
ret.args = createArgs(ret.path);
ret.method = method.toUpperCase();
ret.toSource = function(){ return compileRoute(path, ret); };
return ret;
});
});
// setup boilerplate
var buf = '\n// setup exports\n\n';
for (var method in ret) {
ret[method].forEach(function(route){
if (!route.prop) return;
var parts = route.prop.split('.')
, curr = ['exports'];
parts.forEach(function(part){
var prop = curr.concat(part).join('.');
if (!~buf.indexOf(prop)) {
buf += prop + ' = ' + prop + ' || {};\n';
}
curr.push(part);
});
});
}
// methods
for (var method in ret) {
ret[method].forEach(function(route){
if (!route.prop) return;
buf += '\n' + route.toSource();
});
}
// module boilerplate
return 'var client = function(exports, request){\n'
+ buf.replace(/^/gm, ' ')
+ '\n};';
}
/**
* Parse `path` string, returning objects with a `type`
* and `string` representing the path segments.
*
* @param {String} path
* @return {Array}
* @api private
*/
function parsePath(path) {
return path.split('/').slice(1).map(function(part){
if (':' == part[0] && '?' == part[part.length - 1]) {
return { type: 'optional', string: part.slice(1, -1) };
} else if (':' == part[0]) {
return { type: 'required', string: part.slice(1) };
} else {
return { type: 'segment', string: part };
}
});
}
/**
* Create property name for `path`.
*
* @param {String} path
* @return {String}
* @api private
*/
function createProp(path) {
return path.filter(function(part){
return 'segment' == part.type;
}).map(function(part){
return part.string;
}).join('.');
}
/**
* Filter arguments from `path`.
*
* @param {Array} path
* @return {Array}
* @api private
*/
function createArgs(path) {
return path.filter(function(part){
return 'segment' != part.type;
});
}
/**
* Compile `path` into a javascript string.
*
* @param {String} base
* @param {Array} path
* @return {String}
* @api private
*/
function compilePath(base, path) {
return '"' + base + '/' + path.map(function(part){
switch (part.type) {
case 'segment':
return part.string;
case 'required':
return '" + ' + part.string + ' + "';
case 'optional':
return '" + (' + part.string + ' || "") + "';
}
}).join('/') + '"';
}
/**
* Compile validation for `args`.
*
* @param {Array} args
* @return {String}
* @api private
*/
function compileArgValidation(args) {
return args.filter(function(arg){
return 'required' == arg.type;
}).map(function(arg){
return ' if (null == ' + arg.string + ') throw new Error("' + arg.string + ' is required");';
}).join('\n');
}
/**
* Compile `args`.
*
* @param {Array} args
* @return {String}
* @api private
*/
function compileArgs(args) {
return args.map(function(arg){
return arg.string;
}).join(', ');
}
/**
* Compile the given `route` into javascript.
*
* @param {String} path
* @param {Object} route
* @return {String}
* @api private
*/
function compileRoute(path, route) {
var parts = route.prop.split('.')
, method = route.method.toLowerCase();
if ('delete' == method) method = 'del';
if ('get' != method) route.prop += '.' + method;
var js = '';
js += '/*\n';
js += ' * ' + route.method + ' ' + parts.join(' ') + '.\n';
js += ' */\n';
js += '\n';
js += 'exports.';
js += route.prop + ' = function(' + compileArgs(route.args) + '){\n';
var args = compileArgValidation(route.args);
if (args.length) js += args + '\n';
js += ' return request("' + route.method + '", ' + compilePath(path, route.path) + ');';
js += '\n};\n';
return js;
}
$(function(){
client(window, function(method, url){
return $.ajax({ url: url, type: method });
});
// or you could do:
// var api = {};
// client(api, ...)
// api.users() etc
$('#user').click(function(){
user(3).success(function(res){
alert('got ' + JSON.stringify(res));
});
});
$('#user-repos').click(function(){
user.repos(3).success(function(res){
alert('got ' + JSON.stringify(res));
});
});
$('#users').click(function(){
users().success(function(res){
alert(res.map(function(user){
return user.name;
}).join(', '));
});
});
$('#delete-user').click(function(){
user.del(3).success(function(res){
alert(res.message);
});
});
$('#delete-users').click(function(){
users.del().success(function(res){
alert(res.message);
});
});
});
@kmiyashiro
Copy link

what is this!

@tj
Copy link
Author

tj commented Jul 5, 2011

haha. was hacking together a little automated REST client prototype

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment