2014年10月29日 星期三

『Node.js』介紹 middleware 應用與開發

從我們前幾篇用到 express web framework 框架的時候,express 就已經內建啟用 middleware 的概念,可以利用中間層的這個部分做一些效果,接下來本章節就來介紹  middleware 。首先我們先建立好一個 express web framework 框架

建立 express 的指令是:

$express -e nodejs-middleware

並且打開 app.js 檔案

var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

/// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

/// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

從上面建立的  express 框架可以看到這裡用了許多的 app.use 的方法,包含靜態檔案、body 解析器、路由機制。當預設 middleware 之後,每當瀏覽器收到連線要求的時候,middleware 就會被呼叫啟用。簡單來講 middleware 就像一個薄薄的一層過濾網

建立 middleware 

由於基本設定接已經有提供高效能的套件,其 express  所建立好的 middleware 設定之外,也可以自定撰寫 middleware 安排到基本設定流程當中,我們建立一個簡單的 middleware 功能,請在 app.js 安插以下程式:



一開始先建立一個 middleHandler ,其基本參數有  request, response, next,而  next方法 是代表交給下一個 middleware 繼續作處理,接著在建立 app.get ,我們預期要從瀏覽器  /test ㄙㄟ來做請求,第二個參數為 middleHandler function ,以及第三個 callback 函式



一開始從瀏覽器 localhost:3000/test 發送出去之後,會先進到 middleHandler ,所以會先顯示 **start** :: middle handler function ,然後遇到  next(); 之後再回到 app.get  繼續作處理,因此再將 **end** :: middle handler function 顯示,最後用了一個 res.send 方法將一個小字串文字直接輸出到瀏覽器上面。

下圖可看結果:




更多應用,下回待續... 


參考資料:
http://stephensugden.com/middleware_guide/
http://www.hacksparrow.com/how-to-write-midddleware-for-connect-express-js.html

『Node.js』基礎工具核心模組 util.inherits 實現原型繼承

在 js 的內建功能裡面如果有些不適合或無法滿足需求的時候,多半都要動手刻程式然後做到自己要的模組來呼叫方法。而 Node.js 提供了一個函數的集合,能夠比 js 更多的一些基本的處理,我們可以看到以下官方網站所提供 util 可用的方法。在本章節將介紹比較重要關注的方法


util.inherits 實現原型繼承

引用內建基礎工具  util,請在程式第一行寫 var util = require('util’);  方可使用工具的方法。在這邊我們介紹繼承的功能,也就是  util.inherits(constructor, superConstructor),在參數的第一個作為建構子,而第二參數為父建構子,直接來看程式的部分

function fnBase() {
     this.name = 'java';
     this.price = 250;

     this.getBookName = function() {
          console.log("getBookName: the book name is " + this.name);
     };
}

fnBase.prototype.showName = function(){
     console.log("showName: " + this.name);
};

fnBase.prototype.showPrice = function(){
     console.log("showPrice: " + this.price);
};

在這邊開始先建立了一個 fnBase 的物件,包含 name, price 屬性並且給予初始化值以及 getBookName 方法,而這個取得 book 的名稱。另外再多兩個 prototype 的原型方法,一個是 showName 以及 showPrice 方法,接著我們在建立一個物件

function Book() {
     this.name = "nodejs";
}

我們定義好基礎對象為 fnBase 之後,以及繼承 fnBook  的 Book 建構子的屬性 name 為 nodejs,我們在透過以下方法來塑造繼承關係

util.inherits(Book, fnBase);

接下來呼叫一下看看會有什麼變化,首先我們先建立 fnBase  的物件,在呼叫內部包含的方法,showName 及 getBookName

var obj = new fnBase();
obj.showName();
obj.getBookName();
console.log(obj);

其結果如下,可以看到呼叫的兩個方法皆有顯示答案




最後將 new fnBase() 換成  new Book() 看看,程式如下:

var book = new Book();
book.showName();
book.getBookName();
console.log(book);

Oops! 為什麼會出錯?結果居然只呼叫得到 showName, 卻無法取得  getBookName.




原因是因為 Book  只會繼承 fnBase 在原型 prototype 的函數,其建立的屬性與 getBookName 並不會被 Book 繼承。因此在繼承上的觀念則要特別注意了

參考資料:
http://nodejs.org/api/util.html
http://www.w3schools.com/js/js_object_prototypes.asp

『Node.js』讀取 markdown 檔案內容顯示在頁面上

繼上一篇完成讀取檔案的操作之後,本篇將介紹讀取  markdown 檔案內容顯示在頁面上面。

話說前頭,什麼是 markdown?

從 wiki 網站所說的,它是一種輕量級的標記語言,允許使用容易讀也容易寫的純文字的格式,最後轉換 xhtml, html 檔案,就有點像是一般電子郵件純文本的特性。可以從這個網址來 http://markdown.tw/ 學習每個元素的寫法。

node-markdown 實作

首先我們建立一個 express web framework 的專案,在這邊的樣板引擎是用 ejs ,因此下的指令是 

$express -e nodejs-fs-markdown

在根目錄底下建立一個 markdown 檔案,名為 book.md 的檔案,開啓 book.md 檔案後,我們任意打一些 markdown 的格式預備要來轉換  html 格式




接著重要我們要採用  node-markdown 的套件來安裝,詳見 https://www.npmjs.org/package/node-markdown ,安裝方式可使用

$npm install node-markdown 

其檔案結構會是:




請打開 app.js 檔案,我們要將 node-markdown 引入,因此宣告方式為:

var md = require("node-markdown").Markdown;

並且在 app.js 檔案裡面安插以下這段

app.get('/markdown', function(req, res){
    fs.readFile("./book.md", function (err, data) {
        if (err) {
            throw err;
        }

       //使用  md 方法將檔案內容轉行為 html 格式
        str = md(data.toString());
        console.log(str);

        //回應結果顯示在頁面上
        res.send(str) 
        res.end(); 
    });
});

在這邊使用了 /markdown 作為進入的網址,而這個網址主要是去解析 markdown 的檔案,在這個 function 下,我們還需要利用  fs 核心模組來讀取 book.md 檔案,請務必在宣告的地方加入  var fs = require("fs”); 

讀取的方式就使用  fs.readFile 方法,接著更重要的是再使用 md 方法將檔案內容轉換成  html 格式,最後,看一下結果如下,將會依照 markdown 的格式轉換




學習了這個工具,之後再撰寫 html 的檔案時,可以利用 markdown 的格式來寫,寫起來又輕鬆又好懂喔!
下集待續...

參考資料:
http://markdown.tw/
http://www.wikiwand.com/zh-tw/Markdown


『Node.js』核心模組檔案系統 fs

node.js  提供了檔案操作的模組,包含讀取、刪除、寫入、更名等等的檔案操作。其中 fs 模組他還提供了同步與非同步的方法來使用。他的方法名稱也相當好懂,如果是非同步那麼就 fs.readFile() ,同步的話就是 fs.readFileSync(),相當好區別。本章節將會介紹檔案系統的基本操作,如:

> 讀取檔案 fs.readFile
> 寫入檔案 fs.writeFile
> 附加檔案內容 fs.appendFile
> 更名檔案 fs.rename
> 刪除檔案 fs.unlink

首先我們先建立一個 nodejs-fs 目錄夾,預設檔案結構如下:


content.txt , test.txt 簡單預設寫一些內文,就可以進行以下的檔案系統相關操作:

在操作的宣告裡面,在 index.js 檔案,首先務必要檔案檔案系統模組

var fs = require(‘fs’);

接著我們就可以使用 fs 所提供的方法,方法可參考 http://nodejs.org/api/fs.html

讀取檔案 fs.readFile

var fs = require('fs’); //載入檔案系統模組
//讀取  content.txt 檔案內容
fs.readFile('content.txt', 'utf-8’, function(err, data){
     //若有錯誤就列印訊息
     if (err) {
          console.error(err);
     } else {

          //將檔案內容輸入
          console.log(data);
     }
});

寫入檔案 fs.writeFile

var data1 = "hello, ithome 鐵人賽”;
fs.writeFile(__dirname + '/content.txt', data1, function(err){
     if (err) {
          console.error(err)
     }
});

附加檔案內容 fs.appendFile

var data2 = "的主計林車修該自是";
fs.appendFile(__dirname + '/content.txt', data2, function(err){
     if (err) {
          console.error(err)
     }
});

更名檔案 fs.rename

var oldpath = "content2.txt";
var newpath = "content.txt";
fs.rename(oldpath, newpath, function(err){
     if (err)
          throw err;

       fs.stat('content.txt', function (err, stats) {
         if (err)
              throw err;

         //將檔案的狀態顯示出來
          // stats: {"dev":16777218,
         //      "mode":33204,
         //      "nlink":1,
         //      "uid":501,
         //      "gid":20,
         //      "rdev":0,"blksize":4096,
         //      "ino":33307834,
         //      "size":1254,
         //      "blocks":8,
         //      "atime":"2014-10-21T14:25:18.000Z",
         //      "mtime":"2014-10-21T14:24:56.000Z",
         //      "ctime":"2014-10-21T14:24:56.000Z"
         // }
        
         console.log('stats: ' + JSON.stringify(stats));
       });
});

刪除檔案 fs.unlink

fs.unlink('test.txt', function (err) {
  if (err) throw err;
  console.log('successfully deleted test.txt');
});

事實上檔案系統操作還有很多方法可以使用,可以看到我們

參考資料:
http://nodejs.org/api/fs.html

2014年10月20日 星期一

『Node.js』使用 nodemailer 套件透過 gmail 發送電子信箱

在上幾篇完成了 mysql  的 CRUD 的四大操作,本篇就來個輕鬆一點的應用,在這篇介紹使用 nodemailer 套件透過  gmail 來發送電子信箱,在安裝跟實作上也相當容易,我們透過 https://github.com/andris9/Nodemailer 來講解,在運用上也無需再重新打造輪子,學習如何使用即可。

在這邊先建立一個目錄夾 nodejs-nodemailer ,接著再指令將 package.json 建立起來 (本篇不著重在如何建立 package.json ,如需安裝操作請見 http://ithelp.ithome.com.tw/ironman7/app/article/all/recent/10158140 )。由於我們要安裝 nodemailer 套件,因此下指令

$npm install —save node mailer



其中 —save 會將 nodemailer 相依套件新增到 package.json 檔案裡的 dependencies 項目裡, 如下圖所示:



安裝完成之後,我們就可以來寫程式的部分了,可以看到上圖寫的 "start": “node email” ,在這邊我預定起始點是用 email.js ,因此請在根目錄下建立 email.js 檔案。我們初步的思考是這樣:

> 引入 nodemailer 套件
> 宣告發信的物件 (服務採用 gmail)
> 基本參數設定 (包含內文)
> 送出發信事件

我們直接看一下程式的部分:

//引用 nodemailer
var nodemailer = require('nodemailer');

//宣告發信物件
var transporter = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        user: ‘account@gmail.com',
        pass: ‘yourpassword'
    }
});

var options = {
    //寄件者
    from: ‘account1@gmail.com ',
    //收件者
    to: 'account2@gmail.com', 
    //副本
    cc: 'account3@gmail.com',
    //密件副本
    bcc: 'account4@gmail.com',
    //主旨
    subject: '這是 node.js 發送的測試信件', // Subject line
    //純文字
    text: 'Hello world2', // plaintext body
    //嵌入 html 的內文
    html: '

Why and How

The http://en.wikipedia.org/wiki/Lorem_ipsum" title="Lorem ipsum - Wikipedia, the free encyclopedia">Lorem ipsum
text is typically composed of pseudo-Latin words. It is commonly used as placeholder text to examine or demonstrate the visual effects of various graphic design. Since the text itself is meaningless, the viewers are therefore able to focus on the overall layout without being attracted to the text.',
    //附件檔案
    attachments: [ {
        filename: 'text01.txt',
        content: '聯候家上去工的調她者壓工,我笑它外有現,血有到同,民由快的重觀在保導然安作但。護見中城備長結現給都看面家銷先然非會生東一無中;內他的下來最書的從人聲觀說的用去生我,生節他活古視心放十壓心急我我們朋吃,毒素一要溫市歷很爾的房用聽調就層樹院少了紀苦客查標地主務所轉,職計急印形。團著先參那害沒造下至算活現興質美是為使!色社影;得良灣……克卻人過朋天點招?不族落過空出著樣家男,去細大如心發有出離問歡馬找事'
    }, {
        filename: ‘unnamed.jpg',
        path: '/Users/Weiju/Pictures/unnamed.jpg'
    }]
};

//發送信件方法
transporter.sendMail(options, function(error, info){
    if(error){
        console.log(error);
    }else{
        console.log('訊息發送: ' + info.response);
    }
});

可以看到引入的 nodemailer 的是使用 nodemailer.createTransport 物件先建立好發信的服務 service 為 Gmail,並且透過 auth 建立 gmail 登入的帳號密碼,接著宣告一下收件者 (to)、副本(cc)、密件副本(bcc)、寄件者(from),當然還有主旨 (Subject)、內文(text, html)、附件檔案(attachments)


最後發信結果,就如   package.json 上的 scripts.start 指令,下:
 
$node email



此時,發送結果會如訊息所說,我們再看看信件收到的樣子,由於附件傳送了兩筆,可以看到  unnamed.jpg  及 text01.txt  已經接收到了



其中 text01.txt  是直接在程式上面輸入純文字,我們來看看是否有真的寫入:



更多應用,稍等回來~

參考資料:

http://www.richyli.com/tool/loremipsum/
https://github.com/andris9/Nodemailer

『Node.js』連接 MySQL 並實現 CRUD 操作 - 刪除 (Delete)

前幾篇完成了 Create, Read, Update, 本篇將介紹 Delete 的操作,一樣的我們在使用者列表上面,每筆資料的功能加入『刪除』按鈕,其頁面上,表格列出的語法是:

     mixin users(users)
          h1= title
          fieldset
               legend 以下是從 mysql 取出的使用者列表
               a(href='/create', class='btn btn-primary') 新增使用者
               table.mytable.table
                    th 編輯
                    th 名稱
                    th 描述
                    th 功能
                         each user in users
                              tr
                                   td= user.id
                                   td #{user.name}
                                   td #{user.description}
                                   td
                                        a(href='/update/#{user.id}', class='btn btn-warning') 更新
                                         
                                        a(href='/delete/#{user.id}', class='btn btn-danger') 刪除
              
     - users = user
     .container
          mixin users(users)

刪除部分的語法就是在按鈕點選的時候,連到 /delete ,後面我們指定資料使用者的編號作為參數  /#{user.id}

 a(href='/delete/#{user.id}', class='btn btn-danger') 刪除

佈局如下:








開啓 app.js 補上 delete 的操作

app.get('/delete/:id', function(req, res) {
    pool.getConnection(function(err, connection) {
        connection.query('delete from users  where id = ?', [req.params.id], function(err, fields) {
            if (err)
                throw err;
        });
        connection.query('SELECT * from users', function(err, rows, fields) {
            if (err)
                throw err;
            res.render('user', {
                title : '使用者列表',
                user : rows
            });
        });
        connection.release();
    });
});

其中在程式的部分,在點選按鈕 /delete 的時候,後面接 :id 也就是預定在網址列上面取得使用者編號的參數,然後再此刪除的部分以以下語法呈現:

 connection.query('delete from users  where id = ?', [req.params.id], function(err, fields) {
            if (err)
                throw err;
        }); 

在 connection.query 方法裡的第二參數  [req.params.id] 帶入網址列的參數,最後刪除完成後,再度顯示取得最新的使用者列表

connection.query('SELECT * from users', function(err, rows, fields) {
            if (err)
                throw err;
            res.render('user', {
                title : '使用者列表',
                user : rows
            });
        });

在這邊我們刪掉第四筆資料




結果如下:



我們終於完成了 Create, Read, Update ,Delete 的 mysql 存取操作 ,完整的程式歡迎到這裡 fork 囉 

https://github.com/weijutu/nodejs-mysql-crud

那麼資料庫存取就先到這裡,接下來還有更多應用,敬請期待  :)


『Node.js』連接 MySQL 並實現 CRUD 操作 - 更新 (Update)

上一篇完成了建立資料,本篇就來介紹更新的部分,我們在上一篇的使用者列表頁面上,每一筆資料的功能建立了一個『更新』的按鈕,如下:

在這使用者列表的部分,表格列出的語法是:

     mixin users(users)
          h1= title
          fieldset
               legend 以下是從 mysql 取出的使用者列表
               a(href='/create', class='btn btn-primary') 新增使用者
               table.mytable.table
                    th 編輯
                    th 名稱
                    th 描述
                    th 功能
                         each user in users
                              tr
                                   td= user.id
                                   td #{user.name}
                                   td #{user.description}
                                   td
                                        a(href='/update/#{user.id}', class='btn btn-warning') 更新
                                         
                                        a(href='/delete/#{user.id}', class='btn btn-danger') 刪除
              
     - users = user
     .container
          mixin users(users)

其中也就是在跑 each 時,每筆資料塞入按鈕語法,在這邊我們設定點選的時候連結 /update 而後面再接一個編號參數 /#{user.id}:

a(href='/update/#{user.id}', class='btn btn-warning') 更新

我們簡單看一下表格佈局如下:




並且繼續打開 app.js 檔案,放上 update 的操作

app.get('/update/:id', function(req, res) {
    pool.getConnection(function(err, connection) {
        connection.query('SELECT * from users where id=?',[req.params.id],function(err, rows, fields) {
            if (err)
                throw err;
            console.log('search is success.');
            res.render('create', {
                title : 'Update user',
                user : rows[0]
            });
        });
        connection.release();
    });
});

在這邊先進入到 /update 頁面,預定在打開 update 時,會列出某一筆資料,因此在這邊會先用 sql 語法來查詢某一筆資料,而 id 的部分由網址列的參數取得,也就是 connection.query 的方法的第二參數,是取得網址的參數 [req.params.id] ,完成取得動作之後,顯示在 create 的頁面 (由於上一篇新增的頁面跟本篇的更新頁面欄位相同,所以用的是一樣的頁面)

另外在 render 的參數多了一個 user: rows[0], 主要是用來判別是不是更新的資料,若有資料則是更新、若沒資料就是新增



每項  input 就用 user.[欄位] 將值帶入,顯示部分是:



我們按下儲存之後,將表單送到 /update,因此這邊要做  update 的操作,再度打開 app.js 新增

app.post('/update', function(req, res) {
    pool.getConnection(function(err, connection) {
        connection.query('update users set ? where id = ?', [{
            id : req.body.user.id,
            name : req.body.user.name,
            description : req.body.user.description
        },req.body.user.id], function(err, fields) {
            if (err)
                throw err;
        });
        connection.query('SELECT * from users', function(err, rows, fields) {
            if (err)
                throw err;
            res.render('user', {
                title : '使用者列表',
                user : rows
            });
        });
        connection.release();
    });
});

其中以

        connection.query('update users set ? where id = ?', [{
            id : req.body.user.id,
            name : req.body.user.name,
            description : req.body.user.description
        },req.body.user.id], function(err, fields) {
            if (err)
                throw err;
        }); 

作為 update 的操作,在第二參數時,就是從表單傳送過來的資料,以陣列來表示,裏面每個欄位再以 key/value 呈現,然後最後再

 connection.query('SELECT * from users', function(err, rows, fields) {
            if (err)
                throw err;
            res.render('user', {
                title : '使用者列表',
                user : rows
            });
        });

新增完成後顯示最後的結果列到使用者列表



結果退到使用者列表:



 
更新的操作就完成了,而刪除操作... 下集待續

『Node.js』連接 MySQL 並實現 CRUD 操作 - 建立 (Create)

延續上一篇的連接 mysql  實現 crud 的操作,本篇將介紹新增資料的操作。之後的兩篇文,將會介紹更新、刪除的操作。首先我們在上一篇已經有將資料列出來,在此做一些畫面的調整,使用 bootstrap 的前端框架套件去使用。

本章節主要使用的套件有:

> express web framework
> mysql
> jade

先看一下首頁的佈局,在表格的上面有一個新增使用者的按鈕,預備進到新增的頁面去建立資料,其下方一點的表格,則是列出使用者的列表,預定先寫好每一筆資料的功能,包含更新、刪除,而這兩個功能將在後續的兩章節分別實作



建立資料庫連線的方式,也務必要建立如下:

var mysql = require('mysql');
var pool = mysql.createPool({
    host : 'localhost',
    user : 'nodejs',
    password : 'nodejs',
    database : 'demo_nodejs'
});

我們在本篇只專注於『新增使用者』這個按鈕,而這點選之後會引導到新增的頁面。因此在 app.js 的部分加入:

app.get('/create', function(req, res){
    res.render('create', {
        title: '建立新的使用者'
    });
});
app.post('/create', function(req, res){
    pool.getConnection(function(err, connection){
        connection.query('insert into users set ?', {
            id: req.body.user.id,
            name: req.body.user.name,
            description: req.body.user.description
        }, function(err, fields){
            if (err)
                throw err;
        });

        //新增完成後,查詢目前所有使用者
        connection.query('select * from users', function(err, rows, field){
            if (err)
                throw err;

            //將資料傳送到首頁的使用者列表
            res.render('user', {
                title: '使用者列表',
                user: rows
            });
        });
        connection.release();
    });
});

在這邊建立兩個 get, post 的方法,我們預計點選按鈕的時候網址指定為 /create ,因此在 『新增使用者』的這個按鈕語法為:

a(href='/create', class='btn btn-primary') 新增使用者

而 app.post 方法的那部分則需要在表單送出的時候 直接將表單提交出去,做 insert 的動作,insert 的參數在第一個參數位置就寫問號,第二參數則使用json 格式的方式做 key/value 的資料,也就是這部分,主要表單傳送出來的資料,用  req.body.user.[欄位],如下:

connection.query('insert into users set ?', {
            id: req.body.user.id,
            name: req.body.user.name,
            description: req.body.user.description
 }, function(err, fields){
     if (err)
        throw err;
}); 
接著建立 view 的部分,在 /views 建立 create.jade 



顯示的部分



就以新增資料之後,再退回到前面列表的頁面,程式的部分將會在 insert 完成後,再執行:

//新增完成後,查詢目前所有使用者
        connection.query('select * from users', function(err, rows, field){
            if (err)
                throw err;

            //將資料傳送到首頁的使用者列表
            res.render('user', {
                title: '使用者列表',
                user: rows
            });
        }); 

這樣將會引導使用者到前頁顯示使用者列表,結果如下,紅色框則是新增的資料