Speaking of functional programming… i’m wondering if anyone would be interested in me open sourcing a little library I wrote for a RESTful API ? I’m not sure how useful this would be.
Personally with a bit more polish I think having this on a headless Meteor server is more productive than an express API (no callbacks!)
I started out with just the Picker package and ran into maintenance problems so I made this.
@awatson1978 i’d love your opinion if you have a few spare mins!
It’s written in a functional style and heavily utilizes ES6 to create a better developer API. It was evolved from the simple:json-routes
package and into something that can be used to easily maintain a large API using Meteor as the base. I’m currently using it to power a set of native and ReactNative apps.
The main features are:
- Functional approach with ES6
- Automatically catch errors and render proper JSON error
- Easy to use router
- Skinny controller to ease testing and to isolate side effects
-
Connection transformation in controller with chaining functions (like change header data)
- Changeset integration with SimpleSchema
- Optional JSON view layer for response data munging
- CLI generator (with passing tests) if it would get open-sourced
Also some non-router things that I used are (and perhaps a sep package):
- Repository pattern to abstract away database & throw errors if $ is prepended, eg.
$insert
- Model layer (works with changeset to validate insert/updates)
Here’s the gist of how it works:
Router
const {scope, get, post, put, delete_} = Restful;
applyMiddleware(
acceptJSON(),
authenticateJWT({except: 'post.user'}),
);
scope('/v1', () => {
// resource saves boilerplate using conventions
resources('/users', UserController, {except: 'delete'})
resources('/comments', CommentController)
resources('/posts', PostController)
// or manually add routes
get('/items/', ItemController, 'index')
post('/items', ItemController, 'create')
get('/items/:id', ItemController, 'show')
delete_('/items/:id', ItemController, 'delete')
put('/items/:id', ItemController, 'update')
})
Controller
const {sendJson, putStatus} = Restful;
const {Item} from Models;
const {ItemView} from Views;
ItemController = {
debug: true, // prints params and return value
scrubParams: [...], // pending, will reject all contrl reqs based on rules
index(conn, params) {
// Repository pattern abstracts persistence dep. and removes boilerplate
const items = Repo.all(Item);
// allows a json 'view' to munge data outside of controller (optional)
return renderView(conn, ItemView, items);
//or just directly send: `sendJson(conn, items)`
},
create(conn, params) {
// you could use Repo.$insert to remove error handling
// boilerplate, see #show & #delete actions
const [status, result] = Repo.insert(params);
if (status === 'ok') {
return sendJson(conn, result);
} else {
return sendJson(conn, 500, {error: result})
}
},
update(conn, params) {
const [status, result] = Repo.update(Item, params.id, params);
if (status === 'ok') {
return sendJson(conn, result);
} else {
return sendJson(conn, 500, {error: result});
}
},
show(conn, {id}) {
// $get will throw and return json error if non-existent
// or get will return undefined if non-existent
const item = Repo.$get(Item, id);
return sendJson(conn, item);
},
delete(conn, {id}) {
Repo.$delete(Item, id);
return sendJson(conn, {});
}
};
Repo layer (Optional)
Too much for this post, see gist:
https://gist.github.com/AdamBrodzinski/9e667d14e980df7c0fbb
JSON View layer (Optional)
const {renderMany, renderOne} = Restful;
// you can munge data as needed without concerning the database
// this makes testing the transformation much easier
// ex in controller:
//
// return renderView(conn, ReminderView, reminders);
//
ReminderView = {
// can use conn to determine auth/user/params/etc..
// munge ex: add 'data: {}' wrapper, convert time to unix timestamp, etc..
render(conn, reminder) {
return {
_id: reminder._id,
scheduledAt: reminder.scheduledAt,
userId: reminder.userId,
userName: reminder.userName,
userNumber: reminder.userNumber,
message: reminder.message,
recurFrequency: reminder.recurFrequency,
active: reminder.active,
recipients: reminder.recipients,
sentLog: reminder.sentLog
};
}
}