什麼是 NodeJS 我想我不要再多做介紹了,之前有寫過一兩篇文章,如果不知情的讀者可以先去看一下。
然後什麼是 Coke,這是今天要講的東西,所以我們繼續看下去。
Framework
ExpressJS 這一套 Web Server 我想算是 NodeJS 裡面比較熱門的一款。當然最近可能還是有不少新起之秀,不過怎麼樣來說他還是目前大宗。
而今天要說的 Coke 則是 Ben Lin 這位高手自行開發的一套 Framework,底子裡面是 ExpressJS,然後加上了許多魔術的方法。Ben 自己是開發 API 居多,而我這次是拿來全力開發一般型態的網站。
Special
既然會挑選這一套來使用,必然有許多特別的地方:
- thunder 樣板引擎,作者一樣是 Ben,功能不多,但是速度相當快!
- mongoose
- Generator 支援 project, controller, model, scaffold
- 完全支援 ExpressJS, Connect
- 可以自行加入 Middleware
- Router 支援 resources(Ben 說是借 RailwayJS 來用的
- Controller 支援 before, after 兩種 Filter
- 支援 Assets
- 支援 Cluster
- 還有更多請看 github
雖然說 Coke 寫著往後可能會使用 mongoskin,不過,雖然 mongoose 要額外去設定 Schema,這一點是跟 NoSQL 的 schema-free 有點背道而馳,但,我覺得並無不妥。
關於 schema-free 這裡有篇小品:
Schema-Free MySQL vs NoSQL
樣板引擎我就暫時先跳過,留待下一次充版面用。當時跟 Ben 聊到這個,他自嘲的說,這個樣板引擎跟 PHP 的寫法很像。我實際使用上來看,確實有點雷同(笑,不過,由於 Ben 都是用 res.json
居多,所以反而我是這個樣板引擎的重度使用者。
然後 before
與 after
這兩種 Filter 真是讓人驚豔。以往在寫 cakePHP 的時候,會有類似的概念,沒想到在這裡 Ben 直接把他實作出來了,真是佛心來著(蓋章
至於這個 Filter 是幹嘛用的?簡單的來說,before
就是在你的 action
之前做事,after
則在其後做事。至於這麼做的好處是什麼?不要問,很恐怖。
Middleware 可以自己擴充這件事情,真是太棒了(容後再述
關於 Router 這個部份,其實 Ben 只是想用 resources
這個功能而已,他並無意真的要自己硬幹一個 Router 出來,畢竟 ExpressJS 的 Router 功能已經很完善了。他的理由是,他寫 API 會用到(笑
支援 Assets,然後可以分組,這個明眼人應該就知道是跟誰借來用的,等我聊到樣板引擎的時候再一起講這個部份好了。
Installaction
有些前置需求要注意的地方請自己看喔!
sudo npm install -g coke
Quick Start
coke n cokeapp
cd cokeapp
npm install -l
NODE_ENV=dev coke s
然後用瀏覽器開啟 http://localhost:4000
,打完收工。
Structure Overview
這是我們剛做完一個新的專案所產生的目錄結構,
▾ app/
▸ controllers/
▸ helpers/
▸ libs/
▸ locals/
▸ middlewares/
▸ models/
▸ views/
▾ config/
▸ dev/
▸ prod/
▸ test/
assets.yml
routes.js
▾ db/
schema.js
▸ doc/
▸ log/
▸ node_modules/
▾ public/
▸ assets/
▸ css/
▸ img/
▸ js/
apple-touch-icon-114x114-precomposed.png
apple-touch-icon-129x129-precomposed.png
apple-touch-icon-57x57-precomposed.png
apple-touch-icon-72x72-precomposed.png
apple-touch-icon-precomposed.png
apple-touch-icon.png
favicon.ico
Initial BUG
如果你的 NODE_ENV
是 production
的話,你應該會發現有一點奇怪,怎麼 css
, js
, img
都讀不到了?嗯,你可以比對一下 config/dev/express.js
與 config/prod/express.js
看看有何差異?
你應該會發現 prod/express.js
少了一行靜態檔案設定,
app.use( express.static( PUB_DIR ));
加上去就沒事了。這應該是 Ben 故意的吧我想 XD
Controller
產生 Controller 的方式很簡單,
$ coke g controller hello index world
path.existsSync is now called `fs.existsSync`.
exists app/
exists app/controllers/
exists app/views/
create app/views/hellos/
create app/views/hellos/index.html
create app/views/hellos/world.html
create app/controllers/hellos.js
update config/routes.js
規則很簡單,就是 controller
+ action
+ action
這種模式依此類推。首先你會得到一個 hellos.js
在你的 app/controllers/
底下,然後他會照你給的 action
幫你把 views
給建立好,最後更新 routers.js
。
我們來看 routes.js
變成什麼樣子,
module.exports = function ( map ){
map.get( 'hellos/index', 'hellos#index' );
map.get( 'hellos/world', 'hellos#world' );
map.get( '/','welcome#index' );
};
這裡的 map.get
你可以用 ExpressJS 的 route 來理解他,後面的 hellos#index
所代表的是 controller#actoin
,這樣應該不難理解。同樣的,這裡跟 ExpressJS 一樣,在 route 的部份也可以使用正規表示式來完成。
舉例來說,
module.exports = function ( map ){
map.get( 'hellos/index', 'hellos#index' );
map.get( 'hellos/:world?', 'hellos#world' );
map.get( '/','welcome#index' );
};
這樣一來,後面的 :world?
則是一個正規表示式,在 world
這個 action 中,用 req.params
可以拿到 world
這個 KEY 的相對應值。至於這邊正規表示式要怎麼寫,我就不贅述了。
Model
關於資料庫的部份呢,產生方式也不麻煩,
$ coke g model hellos
path.existsSync is now called `fs.existsSync`.
exists app/
exists app/models/
update db/schema.js
create app/models/Hello.js
請注意 MongoDB 的保留關鍵字,db 或是 collection 請不要使用到一樣的名字,不然你在 mongo cli 底下會想撞牆!
我剛剛說過,由於 mongoose 是需要設定 schema 的,所以可以看看 db/schema.js
有些什麼東西,
var Model = {
Hello : new Schema({
created_at : { type : Number, 'default' : Date.now },
updated_at : { type : Number }
})
};
這是剛剛新增的,然後我們在 models
資料夾下會有個 Hello.js
,
var Hello = require( BASE_DIR + 'db/schema' ).Hello;
require( 'mongoose' ).model( 'Hello', Hello );
然後我們將 Controller 與 Models 組合起來,大概會是這樣,首先是 Controller,
var Application = require( CONTROLLER_DIR + 'application' );
var Hello = require( 'mongoose' ).model( 'Hello' );
module.exports = Application.extend({
index : function ( req, res, next ){
res.render( 'hellos/index' );
},
world : function ( req, res, next ){
Hello.show( 'world',
function( err ) {
req.flash( 'error' );
res.render( 'hellos/world', { world : null } );
},
function( world ) {
res.render( 'hellos/world', { world : world } );
}
);
}
});
然後是我們的 Model,
var Hello = require( BASE_DIR + 'db/schema' ).Hello;
Hello.static = {
show : function( world, error, success ) {
this.findOne({
name : world
}, function( err, world ) {
if( err ) return error( err );
return success( world );
});
}
};
require( 'mongoose' ).model( 'Hello', Hello );
這樣就可以把 Model 跟 Controller 串起來了。
View
由於筆者要外出取材遛狗,所以下次再說。
小結
整體來說 Coke 是一個相當不錯的 framework,我目前用於開發上,雖然遇到一些問題,不過其實到最後都不是什麼大問題。畢竟有時候,可能會覺得某個語言的某種特性很好,但是如果每一種方法都拿來用,那這樣的框架似乎就有點太過於冗長了。
以目前的狀況而言,Coke 真的非常好用,如果要hack source看看原始碼也是相當方便的,畢竟我也踩了不少雷。不過由於 Ben 太忙沒時間寫 doc 所以大家就原諒他吧(笑