Brief
In MVC Controllers just delegate the tasks for the right parts of the source code. But validate inputs and other things are located in that, for me its a dummy way to do this things, because your controller increase your weight with this unnecessary things and Hapi got a cool Academy for make your controller more Thin!
Thin Controller
Hapi provide us some tools for our controller lose weight, the most part of this tools is granted by the Hapi Request Life-Cycle, but this is an subject for another post.
When receive an incoming request, we need validate ours inputs, check the auth token or something like this. Check my routes/todo.js bellow:
'use strict'; | |
const Controller = require('../controllers/todo'); | |
exports.register = (server, options, next) => { | |
// instantiate controller | |
const controller = new Controller(options.database); | |
server.bind(controller); | |
server.route([ | |
{ | |
method: 'GET', | |
path: '/todo/{id}', | |
config: { | |
handler: controller.get, | |
validate: { | |
params: { | |
id: Joi | |
.string() | |
.alphanum() | |
.regex(/^(?=[a-f\d]{24}$)(\d+[a-f]|[a-f]+\d)/i, '_id') | |
.required() | |
} | |
} | |
} | |
}, | |
{ | |
method: 'POST', | |
path: '/todo', | |
config: { | |
handler: controller.create, | |
validate: { | |
payload: { | |
name: Joi | |
.string() | |
.min(1) | |
.max(30) | |
.trim() | |
.required(), | |
checked: Joi | |
.boolean() | |
.default(false) | |
.optional() | |
} | |
} | |
} | |
} | |
]); | |
next(); | |
}; | |
exports.register.attributes = { | |
name: 'todo-route', | |
version: '1.0.0' | |
}; |
Just for example, consider we have an auth system default based on JWT. In other opportunity I can talk about it.
Well, our routes need be authenticated, because we defined it before and our JWT auth is default, if we need in a route disable, just adding a line before handler like this:
auth: false
And this specified route are not validated with our auth system, very useful in cases like SignIn, SignUp.
So, my favorite feature provided by Hapi is the validate in route object, because in validate we can check all the needed parameters in payload, querystring, headers and others, you can see more options here.
Joi is part of this, and you can define a lot of validations for yours parameters using it.
Like conditional parameters, complex objects internals, arrays, and all of yours necessities want!
Power Resumed
In resume about all I said above, all validations and auth logic is provided by Hapi for you don't worry about this simple things. By the Request Life-Cycle if any of validation not are passing, your code are not executed. Fail fast!
In your controller you will assume all things are ok and then you can produce a Thin controller like this:
'use strict'; | |
function TodoController (db) { | |
this.database = db; | |
this.model = db.Todo; | |
} | |
TodoController.prototype = { | |
get, | |
create | |
}; | |
module.exports = TodoController; | |
// [GET] /todo/{id} | |
function get (request, reply) { | |
let userId = request.auth.credentials.id; | |
let id = request.params.id; | |
this.model.findOneAsync({_id: id, owner: userId}) | |
.then((todo) => { | |
if (!todo) { | |
return reply.notFound(); | |
} | |
reply(todo); | |
}) | |
.catch((err) => { | |
reply.badImplementation(err.message); | |
}); | |
}; | |
// [POST] /todo | |
function create (request, reply) { | |
let userId = request.auth.credentials.id; | |
let payload = request.payload; | |
payload.owner = userId; | |
this.model.createAsync(payload) | |
.then((todo) => { | |
reply(todo).code(201); | |
}) | |
.catch((err) => { | |
reply.badImplementation(err.message); | |
}); | |
}; |
You can get all parameters in your request with no guilty, because Hapi check all parameters if its are correct and have the expected values for you.
If you have a better way to do this things, please say in comments, twitter or github, because comments help me to improve and say to anothers about best pratices!