Outline.js
npm install -save outline.js
setup
Outline is a very opinionated piece of framework, so there is a lot of stuff you have to do before you start.
1) folder structure
You have to put files in their right places and with right names, otherwise it won't work.
Basic folder structure looks like this
.
├── app
| ├── apis
| | └── accountsApi.js
| ├── middlewares
| | └── middleware.js
| ├── models
| | └── homeModel.js
| ├── schemas
| | └── schemas.js
| └── views
| ├── homeView.html
| └── dashboardView.html
├── config
| └── config.json
├── static //you can name this folder as you want, outline will do nothing with it.
├── server.js
└── package.json
Let me explain it:
In apis, models and views folder, you can have more files, but there is just one middleware.js and just one schemas.js.
Apis and models need to have to be named like this: yournameApi.js/yournameModel. Views don't have any naming requirements.
2) Apis, models, middlewares.
a simple api file looks like this:
var stapes = require('stapes');
var Api = require('outline.js').getApiBase();
var testApi = Api.subclass({
constructor: Api.prototype.constructor,
_init: function() {
console.log('I\'m something like constructor, but since stapes child classes do not call its parent\'s constructor, I use this')
}
test:function(data, cb) {
cb({message:'This nice JSON was server to you by testApi\'s function test'});
},
foo:function(data, cb) {
cb({message:'Param blah is '+data.params['blah']});
}
_private: function() {
console.log('Yey! I'm private, and can be called just from __init function, I'll explain meaning of this later);
}
});
module.exports = testApi;
and this is sample model:
var stapes = require('stapes');
var Model = require('outline.js').getModelBase();
var homeModel = Model.subclass({
constructor: Model.prototype.constructor,
_init:function() {
// _init again
},
main:function(data, cb) {
cb({view:'homeView', data:{text:"Hello world, this message was served by my awesome framework"}});
},
test:function(data, cb) {
cb({view:'homeView', data:{text:data.params['action']}});
},
_test:function(data, cb) {
//another private function
}
});
module.exports = homeModel;
and middleware looks like this:
var Middleware = require('outline.js').getMiddlewareBase();
var Mw = Middleware.subclass({
constructor: Middleware.prototype.constructor,
_init:function() {
// _init once again
},
auth: function(req, res, next) {
//do some authorizing
}
});
module.exports = Mw;
Middleware file has to be named middleware.js
3) server.js
var Outline = require('outline.js'); //require outline
var app = new Outline(); //create outline instance
var port = 3000;
app.middleware('auth', {includes:['/dash/:'], excludes: ['/dash/sfuff']});
app.static('/static', __dirname+'/static') //you know that frome express
app.setup({
'developmentIP': 'localhost',
'author': 'Jan Polášek',
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'GET, OPTIONS',
'Access-Control-Allow-Headers':'Content-Type'
});
app.controller('/', {static:'homeView'});
app.controller('/home/:action', {name:'home', action:':action'});
app.api('/test/foo/:blah', {name:'test', action:'foo'});
app.api('/test/:action', {name:'test', action:':action'});
app.listen(port, function () {
console.log('Listening on port ' + port);
}) //you also know this one from express.
3.1) app.middleware
In sample file above, it'll call Middleware.auth() function just for urls matching selectors from include, excluding urls from exclude. this properties object is optional and you can use just include / just exclude / both.
3.2) app.setup
Right now, supports only params named above. "author" string will be sent in X-developed-by HTTP header.
3.3) app.controller
first argument is selector (you know them from express) and the second one is params object.
In first example
app.controller('/', {static:'homeView'});
it says to render homeView.html without running model function. It is usefull if homeView is just a regular html without any data, that should be passed to a template engine (Nunjucks);
In second example
app.controller('/home/:action', {name:'home', action:':action'});
it says to call function from homeModel. Take a look at that action:':action' - you you provide a colon there, it will be replaced by matching URL parameter. Template name is being set inside model function, as you can see above in sample model file.
And this is why there are private functions. If there is _action() function in homeModel and user sends request to a /home/_action url, it'll throw 404 instead of calling _action function.
4) Database:
if you specify connection URL in config.json like this:
{
"DB_CONNECTION_URL":"mongodb://sample:pradelnakobylisy@somewhere"
}
Outline will automatically connect to mongoDB and this is where you need schemas.js
var schemas = {
'user': {
username: {type:String, unique:true, required:true},
email: {type:String, unique:true, required:true},
password: {type:String, required:true},
}
}
module.exports = schemas;
These are regular Mongoose schemas, but you don't have access to a Mongoose.ObjectId object, so you have to live with id's being stored in schemaName._id. Outline supports checking for unique fields out of box, for more information, take a look at mongoose-unique-validator
everywhere in apis, models and middlewares, you have access to these schemas in this.Db object, so for example you'll be able to get this schema in this.Db['user'];
##TODOS
- fix grammar in docs
- improve docs
- sample app generator to simplify life
- improve framework itself