[NodeJS] 使用 Coke 快速開發 Part 1

什麼是 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 所以大家就原諒他吧(笑

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei