[NodeJS] 第一次 node 就上手

Javascript 越來越紅,NodeJS 也越來越強大,說真的,在十幾年前怎麼也不會想到 Javascript 這個東西可以在後端執行。但是他就是真的發生了,也許會覺得很神奇,不過,既然有新的東西不斷地冒出來,那麼適時的去接觸也是挺合理的。

是說 JS 寫來寫去都沒有長進的說(抹臉

NodeJS

作者是這位天才 Ryan Dahl,把 Google 的 v8 引擎拿來寫出一套 framework,就叫做 nodejs。至於你說他能幹麼?以他目前快速發展的狀況來看,不知道以後能不能上太空

語言特性是這樣,由於是從 Javascript 演進而來,所以是相當純粹的事件驅動(event driven)的方式在運作,沒有任何的阻塞(blocking)的問題,寫起來跟一般的 Javascript 沒什麼兩樣,就是不斷地 callback、callback、再 callback...

#缺點是

如果不搭配一些模組的話,要實做一個 HTTP Server,幾乎是要從 HTTP 連線協定開始做起。個人覺得,難度是一回事,去理解這些事情又是另一回事。把他列為缺點其實也是嚴重了點,充其量只是說,要去做這些事情稍顯繁雜而已(被揍飛

第一次

既然說是第一次就上手,那麼上述那些複雜的東西就留給大家自己去研究。我們這邊來講一些輕鬆寫意又自然的東西,首先,你可能必須要知道的事情有,

NPM(Node Package Manager)是一個 NodeJS 的套件管理系統,原本與 NodeJS 是拆開來的,後來在 0.6.x 之後就放在一起了。這個東西相當的方便,你需要什麼套件,用他來安裝、移除、更新就可以了。

至於說 CoffeeScript 嘛,因為他是新的東西所以我放了上來(眾歐

首先是要安裝 nodejs,你可以去他的官網下載壓縮檔,下載下來就可以裝了,以下都是以 Ubuntu/node-0.6.14 為例子,

wget -c http://nodejs.org/dist/v0.6.14/node-v0.6.14.tar.gz
tar zxf node-v0.6.14.tar.gz
cd node-v0.6.14
./configure
make
sudo make install

如果不能安裝,你可能需要安裝下列套件,
git-core curl build-essential openssl libssl-dev

當然,身為 Ubuntu 的使用者,加個 PPA 用 apt 來裝也是很合理的,

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs

裝好之後就可以看一下版本,

$ node -v
v0.6.14

$ npm -v
1.1.12

這樣就裝完了。另外,用 PPA 安裝的 NodeJS,我在檢查 NPM 版本的時候會怪怪的,不知道最近有沒有改掉?

Web Server

我們要用 NodeJS 來作一個簡易的 Web Server,但是我不想自己刻協定怎麼辦?沒關係,我們找一些模組來用,

其中 GeddyJS 是比較特殊的,他是一個蠻標準的 MCV 架構的套件,但是我之所以沒有用他的原因,是他的 View 使用的是 EJS,而我用的是另一套叫做 Jade。所以,把他列出來是想說,應該也有不少使用 EJS 的高手,所以放上來給大家參考一下。

至於 connect 則是因為,他有很多 Middleware 可以使用,如果你覺得你用不到,你不裝也是可以的。由於我在後面會講到一些東西,是要使用他的,所以這邊就先裝起來放了。

怎麼安裝?

$ npm install connect
$ npm install express

裝好之後就可以開使用了,什麼?太簡單?沒辦法啊,就這麼簡單。然後我們可以開始寫第一支 js 了,

var express = require("express"),
app = express.createServer(express.logger());

app.get("/", function(req, res) {
    res.sendfile(__dirname + '/index.html', function(err) {
        if (err) res.send(404);
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

然後,我們再準備一支 index.html 來當作我們的輸出,

<h1>Hello NodeJS</h1>

接著就可以把 NodeJS 跑起來了,

$ node web.js

然後用你的瀏覽器打開 http://localhost:3000/ 就看得到結果了。然後或許你就想,哇,好方便,那我就一直寫 app.get 然後不斷地增加 html 檔案,就可以做完靜態網站了耶!


錯!

萬一我要用圖片怎麼辦?我的 Javascript 外部檔案怎麼辦?我的 CSS 檔案怎麼辦?沒關係,哥哥我都幫你想好了,

var express = require("express"),
app = express.createServer(express.logger());

app.get("/", function(req, res) {
    res.sendfile(__dirname + '/index.html', function(err) {
        if (err) res.send(404);
    });
});

app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) {
    res.sendfile(__dirname + "/" + req.params[0] + "." + req.params[1], function(err) {
        if (err) res.send(404);
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

嗯,我們用一點小小地正規,只要把網址過濾一下,並且將合法的檔案直接送往前端,這樣就解決囉!至於你說會不會有 mime-type 的問題?嗯,這些事情他已經幫你解決掉了,除非你是使用相當特殊的文件,否則不會遇到檔案類別問題。

歐,我們的靜態網站做完了,可以收工了(喂

使用樣板

每次都要寫一大堆 .html 檔案,當你的檔案越來越多的時候,改起來就相對麻煩。而且,我們也想把一些變數傳進去用,這個時候樣板引擎就是個解決方法。Express 所支援的樣板引擎相當多元,

這裡有剛剛提到的 EJS 跟 Jade,而其實我現在慢慢地再改用 CoffeeKup,所以,這裡我挑了 Jade 來作一些簡單介紹。喔,為什麼不是介紹 CoffeeKup?因為這樣我還要再講一次 CoffeeScript,所以我還是講 Jade 就好了(飄走

我們在 Express 要怎麼使用他呢?請看底下簡單範例,

var express = require("express"),
app = express.createServer(express.logger());

app.configure(function() {
    app.set("views", __dirname + "/views");
    app.set("view engine", "jade");
});

app.get("/", function(req, res) {
    res.sendfile(__dirname + '/index.html', function(err) {
        if (err) res.send(404);
    });
});

app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) {
    res.sendfile(__dirname + "/" + req.params[0] + "." + req.params[1], function(err) {
        if (err) res.send(404);
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

我們將 View 的資料夾設定在 views 底下,然後將樣板引擎指定為 jade,這樣,我們就可以開始寫一個 index.jade 這樣的樣板檔案了。請記得,檔案是放在 views 資料夾下面的,

h1 "Hello NodeJS"

這樣就可以了嗎?當然不是,我們還要修改一下 web.js 才行,

var express = require("express"),
app = express.createServer(express.logger());

app.configure(function() {
    app.set("views", __dirname + "/views");
    app.set("view engine", "jade");
});

app.get("/", function(req, res) {
    res.render("index", {layout: false});
});

app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) {
    res.sendfile(__dirname + "/" + req.params[0] + "." + req.params[1], function(err) {
        if (err) res.send(404);
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

這樣就可以運行了,使用樣板的好處是,你不用一直重新啟動 node 的服務,只要改了樣板內容,他就會做改變。並不會因為他是樣板引擎而需要重新啟動 node 的服務。那,我要怎麼傳變數進去呢?請看,

app.get("/", function(req, res) {
    res.render("index", {
        layout: false,
        nickname: "閃光洽"
    });
});

樣板要怎麼接收呢?

h1= nickname

關於 Jade 還有相當多的變化可用,在他的 gitHub 說明中寫得還算詳盡,包含了陣列、邏輯判斷、字串處理等等。想要知道更多詳細操作的人,可以去他們的 gitHub 看看。

WebSocket

因為新的瀏覽器開始實做 websocket 的部份(我沒有說 IE),所以 NodeJS 在一開始就對這一塊有部份的支援,不過因為當初改版頻繁,所以有些東西其實寫完之後,改版就爛掉的事情時常發生,也被人詬病。


BUT!

救星出現了,他叫做 socket.io,你應該很好奇他要做什麼?舉個簡單的例子,聊天室那麼多人一來一往,訊息即時性的發佈與傳遞,就可以靠他來完成。其實 NodeJS 本身就有這種機制,只是當初並不完善,而有這些模組的助力,讓這些事情變簡單而已。

當然是要安裝的,

$ npm install socket.io

然後就請看官方的範例(喂,其實我說正格的,官方的範例相當完整。這個模組其實只是替我們做掉了 NodeJS 關於 Event 這個部份而已,他並沒有很囉唆的做很多事情。

而且他的好處是,我也可以拿去給前端用,就此,聊天室一來一往的傳送就可以串起來了。當然,WebSocket 可以做的事情不只聊天室,只是舉這個例子比較容易理解而已。所以我貼一下官方範例,應該不過份吧 XD

var express = require("express"),
app = express.createServer(express.logger()),

io = require("socket.io").listen(app);

app.configure(function() {
    app.set("views", __dirname + "/views");
    app.set("view engine", "jade");
});

app.get("/", function(req, res) {
    res.render("index", {layout: false});
});

app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) {
    res.sendfile(__dirname + "/" + req.params[0] + "." + req.params[1], function(err) {
        if (err) res.send(404);
    });
});

io.sockets.on("connection", function (socket) {
    socket.emit("front-end", { hello: "world" });
    socket.on("my other event", function (data) {
        console.log(data);
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

我們的 index.jade 也要改一下,

!!! 5
html
  head
    meta(charset="utf-8")
    title "Hello Socket.IO"
    script(src="/socket.io/socket.io.js")
  body
    h1= nickname
    script
      var socket = io.connect();
      socket.on("front-end", function (data) {
          console.log(data);
          socket.emit("my other event", {my: data});
      });

這個 index.jade 也改太多了吧,其實我就只是把他寫得比較規矩一點而已,沒有改很多啦。這個範例做了兩件事,

  • 主機監聽了一個叫做 my other event 事件。
  • 當連入 / 的時候,主機觸發了 front-end 這個事件。
  • 用戶端監聽了一個叫做 front-end 事件。
  • 當該事件觸發時,會再觸發 my other event 這個事件。

這樣知道他們兩個人之間發生什麼事情了嗎?其實就像是打來打去鬧來鬧去戳來戳去的小情侶而已嘛。

關於資料庫

當然是用目前最熱門的 Mongo 資料庫,其實你要用 MySQL 也不是不行,

不過這裡以 MongoDB 為例子,當然,你的系統需要安裝 MongoDB,才能用囉!

$ npm install mongodb

應用程式的部份稍做修改,

var express = require("express"),
app = express.createServer(express.logger()),

io = require("socket.io").listen(app),

mongodb = require("mongodb"),
mongodbServer = new mongodb.Server("localhost", 27017, {
    auto_reconnect: true,
    poolSize: 10
}),

db = new mongodb.Db("mydatabase", mongodbServer);


app.configure(function() {
    app.set("views", __dirname + "/views");
    app.set("view engine", "jade");
});

app.get("/", function(req, res) {
    res.render("index", {layout: false});
});

app.get(/(.*)\.(jpg|gif|png|ico|css|js|txt)/i, function(req, res) {
    res.sendfile(__dirname + "/" + req.params[0] + "." + req.params[1], function(err) {
        if (err) res.send(404);
    });
});

io.sockets.on("connection", function (socket) {
    socket.emit("front-end", { hello: "world" });
    socket.on("my other event", function (data) {
        console.log(data);

        db.open(function() {
            db.collection("test", function(error, collection) {
                collection.insert({
                    data: data
                });
                db.close();
            });
        });
    });
});

port = process.env.PORT || 3000;
app.listen(port, function() {
    console.log("Listening on " + port);
});

以上的例子,是當用戶端觸發了 my other event 的時候,會將用戶傳來的 data 儲存在 mydatabase 這個資料庫中,且儲存的集合是 test,欄位的名稱叫做 data

當然,例子太簡單就不好玩了,這裡面有一個 Bug。

這裡有一篇關於 NodeJS 與 MongoDB 的文章,可以參考:
NodeJS 與 MongoDB 的邂逅

小結

NodeJS 對這個世界的衝擊當然不只如此而已,Javascript 變成顯學這件事情可能也是始料未及。不過,這些事情總是會讓人興奮的,就端看未來會如何去發展了!

結果到最後我真的沒提到 CoffeeScript

有 Bug 才叫做人生。

推廣

2012 JSDC.tw - 台灣第一界 JavaScript 研討會 歡迎大家來(報名好像額滿了說

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