Browse Source

feature: creating actual routes for each table instead of mapping from request params

version : 0.0.4
pull/8/head
oof1lab 7 years ago
parent
commit
7c8b97bd13
  1. 18
      README.md
  2. 2
      lib/util/cmd.helper.js
  3. 163
      lib/xapi.js
  4. 27
      lib/xsql.js
  5. 2
      package.json

18
README.md

@ -59,18 +59,18 @@ Root URL (localhost:3000/) returns all REST API urls for each table in schema.
## CRUD APIs Usual Suspects ## CRUD APIs Usual Suspects
* GET       /api/:tableName * GET       /api/tableName
* POST      /api/:tableName * POST      /api/tableName
* GET       /api/:tableName/:id * GET       /api/tableName/:id
* PUT       /api/:tableName/:id * PUT       /api/tableName/:id
* GET       /api/:tableName/count * GET       /api/tableName/count
* GET       /api/:tableName/exists * GET       /api/tableName/exists
* GET       /api/:parentTable/:id/:childTable * GET       /api/parentTable/:id/childTable
* DELETE  /api/:tableName/:id * DELETE  /api/tableName/:id
* POST     /dynamic * POST     /dynamic
## Other APIS ## Other APIS
* GET      /api/:tableName/describe * GET      /api/tableName/describe
* GET      /api/tables * GET      /api/tables
## Support for composite primary keys ## Support for composite primary keys

2
lib/util/cmd.helper.js

@ -11,7 +11,7 @@ program.on('--help', () => {
}) })
program program
.version('0.0.3') .version('0.0.4')
.option('-h, --host <n>', 'hostname') .option('-h, --host <n>', 'hostname')
.option('-d, --database <n>', 'database schema name') .option('-d, --database <n>', 'database schema name')
.option('-u, --user <n>', 'username of database / root by default') .option('-u, --user <n>', 'username of database / root by default')

163
lib/xapi.js

@ -18,6 +18,7 @@ class Xapi {
this.mysql.init((err, results) => { this.mysql.init((err, results) => {
this.app.use(this.urlMiddleware)
this.setupRoutes() this.setupRoutes()
this.app.use(this.errorMiddleware) this.app.use(this.errorMiddleware)
cbk(err, results) cbk(err, results)
@ -26,9 +27,27 @@ class Xapi {
} }
urlMiddleware(req, res, next) {
let justUrl = req.originalUrl.split('?')[0]
let pathSplit = justUrl.split('/')
if (pathSplit.length >= 2 && pathSplit[1] === 'api') {
if (pathSplit.length >= 5) {
req.app.locals._parentTable = pathSplit[2]
req.app.locals._childTable = pathSplit[4]
} else {
req.app.locals._tableName = pathSplit[2]
}
}
next();
}
errorMiddleware(err, req, res, next) { errorMiddleware(err, req, res, next) {
if(err && err.code) if (err && err.code)
res.status(400).json({error: err}); res.status(400).json({error: err});
else else
res.status(500).json({error: 'Internal server error : ' + err.message}); res.status(500).json({error: 'Internal server error : ' + err.message});
@ -47,10 +66,8 @@ class Xapi {
root(req, res) { root(req, res) {
//res.sendFile(path + "index.html")
let v = []; let v = [];
v = this.mysql.getSchemaRoutes(false, req.protocol + '://' + req.get('host') + '/api/');
v = this.mysql.getSchemaRoutes(req.protocol + '://' + req.get('host') + '/api/');
v = v.concat(this.mysql.globalRoutesPrint(req.protocol + '://' + req.get('host') + '/api/')) v = v.concat(this.mysql.globalRoutesPrint(req.protocol + '://' + req.get('host') + '/api/'))
res.json(v) res.json(v)
@ -59,36 +76,96 @@ class Xapi {
setupRoutes() { setupRoutes() {
this.app.get('/', this.asyncMiddleware(this.root.bind(this)))
/**************** START : tables apis ****************/ this.app.get('/', this.asyncMiddleware(this.root.bind(this)))
this.app.route('/api/tables') this.app.route('/api/tables')
.get(this.asyncMiddleware(this.tables.bind(this))); .get(this.asyncMiddleware(this.tables.bind(this)));
this.app.route('/api/:tableName/describe')
.get(this.asyncMiddleware(this.tableDescribe.bind(this)));
/**************** END : tables apis ****************/
let resources = [];
resources = this.mysql.getSchemaRoutes(true, '/api/');
for (var j = 0; j < resources.length; ++j) {
/**************** START : basic apis ****************/ let routes = resources[j]['routes'];
this.app.route('/api/:tableName/count')
.get(this.asyncMiddleware(this.count.bind(this)));
this.app.route('/api/:tableName') for (var i = 0; i < routes.length; ++i) {
.get(this.asyncMiddleware(this.list.bind(this)))
.post(this.asyncMiddleware(this.create.bind(this)));
this.app.route('/api/:tableName/:id') switch (routes[i]['routeType']) {
.get(this.asyncMiddleware(this.read.bind(this)))
.put(this.asyncMiddleware(this.update.bind(this))) case 'list':
.delete(this.asyncMiddleware(this.delete.bind(this))); this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.list.bind(this)));
break;
case 'create':
this.app.route(routes[i]['routeUrl'])
.post(this.asyncMiddleware(this.create.bind(this)));
break;
case 'read':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.read.bind(this)));
break;
case 'update':
this.app.route(routes[i]['routeUrl'])
.put(this.asyncMiddleware(this.update.bind(this)));
break;
case 'delete':
this.app.route(routes[i]['routeUrl'])
.delete(this.asyncMiddleware(this.delete.bind(this)));
break;
case 'exists':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.exists.bind(this)));
break;
case 'count':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.count.bind(this)));
break;
case 'describe':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.tableDescribe.bind(this)));
break;
case 'relational':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.nestedList.bind(this)));
break;
}
}
}
this.app.route('/api/:tableName/:id/exists')
.get(this.asyncMiddleware(this.exists.bind(this)));
this.app.route('/api/:parentTable/:id/:childTable') // this.app.route('/api/:tableName/describe')
.get(this.asyncMiddleware(this.nestedList.bind(this))); // .get(this.asyncMiddleware(this.tableDescribe.bind(this)));
/**************** END : basic apis ****************/ //
// /**************** START : basic apis ****************/
// this.app.route('/api/:tableName/count')
// .get(this.asyncMiddleware(this.count.bind(this)));
//
// this.app.route('/api/:tableName')
// .get(this.asyncMiddleware(this.list.bind(this)))
// .post(this.asyncMiddleware(this.create.bind(this)));
//
// this.app.route('/api/:tableName/:id')
// .get(this.asyncMiddleware(this.read.bind(this)))
// .put(this.asyncMiddleware(this.update.bind(this)))
// .delete(this.asyncMiddleware(this.delete.bind(this)));
//
// this.app.route('/api/:tableName/:id/exists')
// .get(this.asyncMiddleware(this.exists.bind(this)));
//
// this.app.route('/api/:parentTable/:id/:childTable')
// .get(this.asyncMiddleware(this.nestedList.bind(this)));
// /**************** END : basic apis ****************/
if (this.sqlConfig.dynamic === 1) { if (this.sqlConfig.dynamic === 1) {
@ -104,7 +181,7 @@ class Xapi {
let query = 'INSERT INTO ?? SET ?'; let query = 'INSERT INTO ?? SET ?';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
params.push(req.body); params.push(req.body);
var results = await this.mysql.exec(query, params); var results = await this.mysql.exec(query, params);
@ -114,12 +191,12 @@ class Xapi {
async list(req, res) { async list(req, res) {
let cols = this.mysql.getColumnsForSelectStmt(req.params.tableName, req.query); let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._tableName, req.query);
let query = 'select ' + cols + ' from ?? '; let query = 'select ' + cols + ' from ?? ';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
query = query + this.mysql.getOrderByClause(req.query, req.params.tableName); query = query + this.mysql.getOrderByClause(req.query, req.app.locals._tableName);
query = query + ' limit ?,? ' query = query + ' limit ?,? '
params = params.concat(this.mysql.getLimitClause(req.query)); params = params.concat(this.mysql.getLimitClause(req.query));
@ -131,15 +208,15 @@ class Xapi {
async nestedList(req, res) { async nestedList(req, res) {
let cols = this.mysql.getColumnsForSelectStmt(req.params.childTable, req.query); let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._childTable, req.query);
let query = 'select ' + cols + ' from ?? where '; let query = 'select ' + cols + ' from ?? where ';
let params = []; let params = [];
params.push(req.params.childTable); params.push(req.app.locals._childTable);
let whereClause = this.mysql.getForeignKeyWhereClause(req.params.parentTable, let whereClause = this.mysql.getForeignKeyWhereClause(req.app.locals._parentTable,
req.params.id, req.params.id,
req.params.childTable); req.app.locals._childTable);
if (!whereClause) { if (!whereClause) {
return res.status(400).send({ return res.status(400).send({
@ -149,7 +226,7 @@ class Xapi {
query += whereClause; query += whereClause;
query = query + this.mysql.getOrderByClause(req.query, req.params.parentTable); query = query + this.mysql.getOrderByClause(req.query, req.app.locals._parentTable);
query = query + ' limit ?,? ' query = query + ' limit ?,? '
params = params.concat(this.mysql.getLimitClause(req.query)); params = params.concat(this.mysql.getLimitClause(req.query));
@ -164,9 +241,9 @@ class Xapi {
let query = 'select * from ?? where '; let query = 'select * from ?? where ';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName,
req.params.id.split('___')); req.params.id.split('___'));
@ -190,9 +267,9 @@ class Xapi {
let query = 'select * from ?? where '; let query = 'select * from ?? where ';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName,
req.params.id.split('___')); req.params.id.split('___'));
if (!clause) { if (!clause) {
@ -225,7 +302,7 @@ class Xapi {
// where clause // where clause
query += updateKeys + ' where ' query += updateKeys + ' where '
let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName,
req.params.id.split('___')); req.params.id.split('___'));
if (!clause) { if (!clause) {
@ -238,7 +315,7 @@ class Xapi {
// params // params
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
params = params.concat(Object.values(req.body)); params = params.concat(Object.values(req.body));
let results = await this.mysql.exec(query, params); let results = await this.mysql.exec(query, params);
@ -252,9 +329,9 @@ class Xapi {
let query = 'DELETE FROM ?? WHERE '; let query = 'DELETE FROM ?? WHERE ';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName,
req.params.id.split('___')); req.params.id.split('___'));
if (!clause) { if (!clause) {
@ -276,7 +353,7 @@ class Xapi {
let query = 'select count(1) as no_of_rows from ??'; let query = 'select count(1) as no_of_rows from ??';
let params = []; let params = [];
params.push(req.params.tableName); params.push(req.app.locals._tableName);
let results = await this.mysql.exec(query, params); let results = await this.mysql.exec(query, params);
res.status(200).json(results); res.status(200).json(results);
@ -308,7 +385,7 @@ class Xapi {
async tableDescribe(req, res) { async tableDescribe(req, res) {
let query = 'describe ??'; let query = 'describe ??';
let params = [req.params.tableName]; let params = [req.app.locals._tableName];
let results = await this.mysql.exec(query, params); let results = await this.mysql.exec(query, params);
res.status(200).json(results); res.status(200).json(results);

27
lib/xsql.js

@ -162,7 +162,7 @@ class Xsql {
} }
if ('_p' in reqParams && parseInt(reqParams._p) > 0) { if ('_p' in reqParams && parseInt(reqParams._p) > 0) {
reqParams._index = (parseInt(reqParams._p)-1) * reqParams._len + 1; reqParams._index = (parseInt(reqParams._p) - 1) * reqParams._len + 1;
} }
//console.log(reqParams._index, reqParams._len); //console.log(reqParams._index, reqParams._len);
@ -362,17 +362,20 @@ class Xsql {
} }
prepareRoute(httpType, apiPrefix, urlRoute) { prepareRoute(internal, httpType, apiPrefix, urlRoute, routeType) {
let route = {}; let route = {};
route['httpType'] = httpType; route['httpType'] = httpType;
route['routeUrl'] = apiPrefix + urlRoute; route['routeUrl'] = apiPrefix + urlRoute;
if (internal) {
route['routeType'] = routeType;
}
return route; return route;
} }
getSchemaRoutes(apiPrefix) { getSchemaRoutes(internal, apiPrefix) {
let schemaRoutes = []; let schemaRoutes = [];
@ -385,17 +388,18 @@ class Xsql {
tableObj['resource'] = tableName; tableObj['resource'] = tableName;
routes.push(this.prepareRoute('get', apiPrefix, tableName)) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/describe', 'describe'))
routes.push(this.prepareRoute('post', apiPrefix, tableName)) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/count', 'count'))
routes.push(this.prepareRoute('get', apiPrefix, tableName + '/:id')) routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create'))
routes.push(this.prepareRoute('put', apiPrefix, tableName + '/:id')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName, 'list'))
routes.push(this.prepareRoute('delete', apiPrefix, tableName + '/:id')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/:id', 'read'))
routes.push(this.prepareRoute('get', apiPrefix, tableName + '/count')) routes.push(this.prepareRoute(internal, 'put', apiPrefix, tableName + '/:id', 'update'))
routes.push(this.prepareRoute('get', apiPrefix, tableName + '/:id/exists')) routes.push(this.prepareRoute(internal, 'delete', apiPrefix, tableName + '/:id', 'delete'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/:id/exists', 'exists'))
for (var j = 0; j < table['foreignKeys'].length; ++j) { for (var j = 0; j < table['foreignKeys'].length; ++j) {
let fk = table['foreignKeys'][j] let fk = table['foreignKeys'][j]
routes.push(this.prepareRoute('get', apiPrefix, fk['referenced_table_name'] + '/:id/' + fk['table_name'])) routes.push(this.prepareRoute(internal, 'get', apiPrefix, fk['referenced_table_name'] + '/:id/' + fk['table_name'], 'relational'))
} }
tableObj['routes'] = routes; tableObj['routes'] = routes;
@ -413,7 +417,6 @@ class Xsql {
let r = [] let r = []
r.push(apiPrefix + "tables") r.push(apiPrefix + "tables")
r.push(apiPrefix + ":tableName/describe")
if (this.sqlConfig.dynamic) if (this.sqlConfig.dynamic)
r.push("/dynamic") r.push("/dynamic")

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "xmysql", "name": "xmysql",
"version": "0.0.3", "version": "0.0.4",
"description": "One command to generate REST APIs for any MySql database", "description": "One command to generate REST APIs for any MySql database",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

Loading…
Cancel
Save