Hi, hi @SkinnyGeek1010. Just wrapped up another project, and wanted to give this some proper thought before replying. Sorry for the delay.
So… curiously interesting. Wouldn’t have necessarily occurred to me to write the REST API in a functional paradigm; but I don’t see why this couldn’t work splendidly. For high volume throughput, I suspect this could work particularly well, without worrying about the garbage collector keeping up. So, a few thoughts and a basic code review:
Namespacing
Functional programming doesn’t mean we need to ditch namespaces. My hunch is that the general approach will be made more clear by introducing a bit of namespace. I’m just making up some names up, but it could look something like this:
RestApi.scope('/v1', function() {
// resource saves boilerplate using conventions
this.resources('/users', UserController, {except: 'delete'})
this.resources('/comments', CommentController)
this.resources('/posts', PostController)
// or manually add routes
Endpoint.get('/items/', ItemController, 'index')
Endpoint.post('/items', ItemController, 'create')
Endpoint.get('/items/:id', ItemController, 'show')
Endpoint.delete_('/items/:id', ItemController, 'delete')
Endpoint.put('/items/:id', ItemController, 'update')
})
Middleware
Very intriguing about the middleware so far. Any chance you could take a look at oauth2orize and make any recommendations on how to get started with an integration? Not asking for a complete implementation. But how difficult would it be to register a grant and some endpoints with your approach? Something like this?
var server = oauth2orize.createServer();
RestApi.applyMiddleware(
acceptJSON(),
authenticateJWT({except: 'post.user'}),
server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) {
var code = utils.uid(16);
var ac = new AuthorizationCode(code, client.id, redirectURI, user.id, ares.scope);
ac.save(function(err) {
if (err) { return done(err); }
return done(null, code);
});
}));
);
Isomorphism & Model,View,Controller
So… this is a pretty standard server-side MVC approach. We’re in agreement with the controller being a controller; but the Models and Views will confuse some people accustomed to client-side MVC paradigms.
const {sendJson, putStatus} = Restful;
const {Item} from Models;
const {ItemView} from Views;
ItemController = { ... }
So, the question is whether this ought to be isomorphic. Should it be available on the client? If so, it’s going to run into the client-side MVC paradigm, which involves ViewPort, device media states, the CSS subsystem, etc. etc. View layer functionality that this paradigm doesn’t necessary take into account. (Not to mention the HTML subsystem and the Document Object Model.) If it’s only going to be server-side, then… yeah, maybe it can make due as-is.
Personally, I’d recommend just scrubbing any reference to Model, View, and Controller, since those terms have a half-dozen different interpretations and are therefore somewhat meaningless and a general cause of confusion.
Other Thoughts on Functional Programming
So, are you familiar with monads yets? The biggest return-on-investment with adding functional programming to javascript apps that we’ve found is when there are clear computational pipelines that need to be created. Generally, we’ve found them with data visualizations and validation testing. We use long method chain pipelines in both our D3 graphs and our Nightwatch test scripts, and they create magic at runtime.
So what’s the computational pipeline that’s involved with the Rest interface? That’s the $64,000 question here. I’m not necessarily suggesting that you create a method chaining pattern. But I also kind of am. It’s sort of the logic culmination of this approach. So what would that look like? What does a RestApi method chain look like? What syntactical verbs are used to create a coherent API syntax?
I’m just tossing some ideas out here, but I could imagine a method chaining pattern that connects with the Mongo collections could be extremely useful:
// example 1
Endpoint.route('/items/:id').collection('Items').get();
// example 2
Endpoint.route('/item/:itemId').collection('Items').findOne({_id:itemId).get();
// example 3
Endpoint.route('/item/:itemId').collection('Items').get(function(){
return Items.findOne({_id: this.params.itemId});
});
// example 4
Endpoint.route('/item/mapreduce').collection('Items').find().fetch().get();
// example 5
Endpoint.route('/item/mapreduce').collection('Items').find().map().reduce().get();
Just some thoughts…