[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 居多,所以反而我是這個樣板引擎的重度使用者。

然後 beforeafter 這兩種 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_ENVproduction 的話,你應該會發現有一點奇怪,怎麼 css, js, img 都讀不到了?嗯,你可以比對一下 config/dev/express.jsconfig/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