2017/07/25

Express MVC

最近心血來潮又把 express 玩了一下,寫篇文章來紀錄一下整個過程,會用到的工具如下:

  1. express 4
  2. node-orm2
  3. nodemon

首先安裝 express-generator,這是一個可以自動產生 express 需要內容的工具

$ sudo npm i -g express-generator

安裝好之後,我們切換到我們想要的目錄來產生專案資料夾,我選用 twig 當作我的樣板引擎,我在 PHP 使用 CI 的時候也是選用同一個引擎,可以無痛轉移

/var/www $ sudo express -v twig project

/var/www $ cd project
/var/www/project $ sudo npm i
/var/www/project $ sudo npm i orm mysql --save

專案目錄產生後,我們可以使用 nodemon 來監控目錄,也可以達到修改檔案後自動重起的效果

/var/www/project $ sudo nodemon bin/www

這時候打開 http://10.10.10.16:3000 (我的 vm 測試 IP)就可以看到預設網址,如果希望改 port 的話可以在 env 設定 port,或者直接去改 bin/www 這個檔案

打開 app.js 這是預設的內容

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

var index = 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', 'twig');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
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 handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

所有的動作都在這隻 app.js 設定,我們來建立一個 product routes 檔案

routes/product.js
const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
    res.render('product/index');
});

module.exports = router;
view/product/index.twig
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h3>This is product page</h3>
</body>
</html>

app.js 加上這個 routes 的設定

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

app.use('/', index);
app.use('/users', users);
app.use('/product', product);

這時網址改成 http://10.10.10.16:3000/product,就會看到 This is product page 這幾個字

接下來我們進行資料庫的串連,在 MySQL 內建立一個叫 express 的資料庫,另外建立一個 products 的 table 並且塞入資料

CREATE TABLE `products` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci;

接下來我們來設定資料庫連接,因為我不喜歡全部的東西都塞在 app.js,所以我另外開一個資料夾來放設置檔

config/db.js
module.exports = (app, orm) => {
    app.use(orm.express("mysql://root:123456@127.0.0.1/express", {
        define: function(db, models, next) {
            models.product = db.define('products', {
                id: Number,
                name: String
            });
            next();
        }
    }));
};

修改 app.js 啟動這個設定,只要放在 app 以及 orm 初始化後即可

app.js
require('./config/db.js')(app, orm);

我們先將 routes/product.js 修改成以下內容,變可以從網址先看到結果

const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
    req.models.product.all((err, data) => {
        res.json(data);
    })
});

module.exports = router;
http://10.10.10.16:3000/product
[{"id":1,"name":"product a"},{"id":2,"name":"product b"}]

這是最基本的用法,但我覺得這樣可重複利用性太低了,所以我們來建立類似 model 機制吧

routes/product.js
const express = require('express');
const router = express.Router();
const Product = require('../model/product.js');

router.get('/', async(req, res, next) => {
    try {
        const product = new Product(req.models.product);
        res.json(await product.getAllNews());
    } catch (e) {
        res.send(e);
    }
});

module.exports = router;
model/product.js
class Product {
    constructor(model) {
        this.model = model
    }

    async getAllNews() {
        return new Promise((resolve, reject) => {
            this.model.all((err, data) => {
                if (err) {
                    reject(err);
                }

                resolve(data);
            })
        })
    }
}

module.exports = Product;

這樣的執行結果會一模一樣,但把東西拆乾淨模組化了,再修改一次在 view 呈現吧

routes/product.js
const express = require('express');
const router = express.Router();
const Product = require('../model/product.js');

router.get('/', async(req, res, next) => {
    try {
        const product = new Product(req.models.product);
        res.render('product/index', {
            products: await product.getAllNews()
        });
    } catch (e) {
        res.send(e);
    }
});

module.exports = router;
views/product/index.twig
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h3>This is product page</h3>
    {% for product in products %}
    <div>product: {{ product.name }}</div>
    {% endfor %}
</body>
</html>

以上就是這兩天試玩 express 的整理,沒有實現所謂的 controller 部份,就先把 routes 當 controller,當然一定會有更多更好的方法,再慢慢探索,在此也感謝陳默司大大的指導