因為工作需要,最近再次學習了node,上一次學習node是2014年,純粹是個人興趣,學了入門之后沒有運用,加上趕別的項目又不了了之。這次正好撿起來。廢話不多說,這里的MEAN指的是Mongodb、Express、Angular和Node。 通過整個項目逐步整合在一起。MEAN棧最大的特色不是運用了哪些框架或第三方,而是前后端都是一種語言,即JavaScript。早些年我也是對node抱着疑態度,覺得這個頁面上操作dom的腳本語言,能扛得起后端那么多模塊嗎?但懷疑不防多了解一下,才決定寫這個系列的文章。
Mongodb做數據存儲,Express是基於node的后端框架,Angular是前端框架,Node是后端運行環境。安裝過程和node的特性就不講了,網上一大把。開發環境是VS2013.安裝了NTVS。node安裝完后,有時候需要設置下環境變量。在cmd目錄下輸入 node -v 如果顯示版本號,則說明安裝正確了。
起始工程
在VS中新建項目,選擇JavaScript-->Node.js,選擇Express4的應用。
為了避免一直Ctrl+C,安裝nodemon,文件更新,它會自動重啟,-g表示安裝成全局。
npm install nodemon -g
修改routes文件夾下的index.js中的title為ReadingClub。然后用cmd切到工程目錄,輸入nodemon啟動工程。
在瀏覽器里面訪問lochost:3000 ,成功打開:
先看routes文件夾下面的index.js ,這就是一個簡單的路由,處理的路徑為“/”,請求方式get,req代表的是request,res代表的response。
render方法有兩個參數,“index”,代表的是要渲染的視圖模板名稱,這里默認的視圖引擎是jade,而后面{title:'ReadingClub'}就是傳遞到視圖的數據模型。這里和Asp.net MVC 的return View() 有些相似,而這里的function就相當 Asp.net MVC中Controller的一個Action。View()默認是對應當前Action名稱的視圖。而render必須指定。
res 也可以直接發回一個響應
res.send('respond with a resource');
建立Controllers
不像Asp.net MVC有默認的路由規則,Express的路由需要一個個配置,不妨把Controller提出來。但在此之前,我們先修改一下目錄。如下,建立app_server文件夾。里面分controllers、views和routes。可以把原來的views和routes直接移進去。
在controllers文件夾中新建一個home.js,加入三個方法:index、books和about。
module.exports.index = function(req, res) { res.render('index', { title: 'Index' }); }; module.exports.books = function(req, res) { res.render('books', { title: 'Books', }); }; module.exports.about = function (req, res) { res.render('about', { title: 'About' }); };
路由
同樣,在view文件夾中把index.jade復制兩遍,修改為books.jade和about.jade. 然后我們修改routes下的index.js,運用Express框架自帶的Router。
var express = require('express'); var router = express.Router(); var homeController = require('../controllers/home'); router.get('/', homeController.index); router.get('/about', homeController.about); router.get('/books', homeController.books); module.exports = router;
這時候還是無法運行的,因為我們改變了目錄結構還沒有在app.js中重新設定。首先設置路由:
var routes = require('./app_server/routes/index'); app.use('/', routes);
修改視圖引擎的起始位置
//app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'app_server', 'views'));
__dirname代表的是根目錄。然后再瀏覽器訪問/books或者/about 。
這樣就分離了controller,請求通過路由抵達控制器,控制器將模型數據填充到對應的視圖的模板.這就是我們熟悉的MVC模式。我們再看router.METHOD方法定義。
router.METHOD(path, [callback, ...] callback)
這里的METHOD指get,post,put和delete等。因為我們可以定義:
router.get('/book/:bookId', homeController.detail); router.put('/book/:bookId', homeController.updateBook); router.delete('/book/:bookId', homeController.deleteBook);
雖然路徑都是一樣,但是代表的是不同的用意,完全restful,:bookId表示是參數。
同樣支持正則匹配,會匹配類似於這樣的‘GET /commits/71dbb9c’
router.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){ var from = req.params[0]; var to = req.params[1] || 'HEAD'; res.send('commit range ' + from + '..' + to); });
如果每個請求都需要做某種處理,可以用all方法:
router.all('*', requireAuthentication, loadUser);
這等價於:
router.all('*', requireAuthentication)
router.all('*', loadUser);
Asp.net MVC的路由每一個都需要設置名稱,且不能重復出現,且匹配到之后就不再匹配,Express沒有這個限制,匹配到之后只要沒返回響應就會向下繼續傳遞。相對而言,Express的Router更靈活一些。
更多細節請參考官方API:http://www.expressjs.com.cn/4x/api.html#router
接下來我們回顧下整個app.js。
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 routes = require('./app_server/routes/index'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'app_server', 'views')); app.set('view engine', 'jade'); // uncomment after placing your favicon in /public //app.use(favicon(__dirname + '/public/favicon.ico')); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(require('stylus').middleware(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', routes); // 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;
1.模塊定義:
首先看到很多的require的語法。
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 routes = require('./app_server/routes/index');
require表示應用一個模塊,npm上已經有超過25萬個包。這些能直接引用的模塊,是已經安裝在node_modules文件中。如果是自己定義的模塊,比如routes 就要用相對路徑。在node中模塊分以下幾類:
- 核心模塊,如http,fs,path等
- .或..開始的相對路徑文件模塊
- 以/開始的絕對路徑文件模塊
- 非路徑形式的文件模塊,如自定義的模塊。
核心模塊會優先加載,以相對或絕對路徑加載的模塊,require都會轉為真實路徑,將編譯執行后的結果放到緩存中,這樣二次加載就會更快。require能加載.js,.node.json的文件,其余擴展名都會被當.js文件載入。模塊與文件是一一對應的,一個文件夾的模塊就稱作包,包通常是一些模塊的集合。require是用來獲取模塊,而exports對象就是用來定義模塊。
module.exports.hello = function() { console.log('Hello.'); };
相當於是定義接口,給外部調用。而上面的路由就是把一整個對象封裝到模塊中。
module.exports = router;
在app.js中直接獲取到整個路由對象:
var routes = require('./app_server/routes/index');
看到module.exports直接賦值router有點奇怪,會想不會覆蓋掉其他的模塊嗎,但事實上在編譯的過程中,node會對獲取的JavaScript文件內容進行包裝,等於是每個文件之間都進行了作用域的隔離。
2.app.set
app.js中用set方法設置了路由起始路徑和視圖引擎。
app.set('views', path.join(__dirname, 'app_server', 'views'));//這里我們修改了路徑在app_server文件夾下
app.set('view engine', 'jade');//默認的視圖引擎是jade
還可以設置路由是否忽略大小寫,默認是不忽略。
app.set('case sensitive routing',true)
還可以設置環境變量是開發環境還是生產環境,更多的設置可以參考官方文檔:http://www.expressjs.com.cn/4x/api.html#app.settings.table
3.app.use
use方法是用來注冊一系列中間件,監聽端口上的請求,中間件利用了尾觸發機制,每個中間件傳遞請求對象,響應對象和尾觸發函數,通過隊列形成一個處理流。
最簡單的中間件形式:
app.use(function (req, res, next) { console.log('Time: %d', Date.now()); next(); })
看下各個中間件的作用:
app.use(logger('dev')); //日志,在開發環境下用彩色輸出響應狀態,會顯示請求方式,響應時間和大小。 app.use(bodyParser.json());//解析json請求。 app.use(bodyParser.urlencoded({ extended: false }));//解析form請求(含有key-value鍵值對),false表示value的類型是string或array,為true表示任意類型。 app.use(cookieParser());//解析cookie app.use(require('stylus').middleware(path.join(__dirname, 'public')));//使用stylus做css預編譯,並指定路徑。 app.use(express.static(path.join(__dirname, 'public')));//靜態文件路徑
4.error
我們看到在設置了路由之后,如果請求還沒返回則認為頁面沒有找到,這個時候app拋出一個error。並繼續往下傳遞
app.use('/', routes); // catch 404 and forward to error handler app.use(function (req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); });
而接下來,對錯誤進行了處理
// 開發環境錯誤處理 // 會打印出錯誤堆棧 if (app.get('env') === 'development') { app.use(function (err, req, res, next) { res.status(err.status || 500);//如果不是404就認為是內部錯誤 res.render('error', { message: err.message, error: err }); }); } // 生產環境錯誤處理 // no stacktraces leaked to user app.use(function (err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); });
檢測到異常的時候,就渲染error模板。 接下來看下error模板,簡單介紹下jade語法:
extends layout //相當於Asp.net MVC 設置Layout
block content //相當於 Asp.net MVC RenderBody
h1= message //顯示message
h2= error.status //顯示錯誤狀態
pre #{error.stack} //錯誤堆棧
這樣就能處理404和500錯誤頁面。
小結:至此,整個默認工程已經介紹完,這一節通過Express框架建立一個基本的MVC工程,了解基本的請求和響應,node的基本模塊和中間件;並初步設置了路由,建立起專門的controller;解讀了app.js中的相關代碼;下一節關注模型和視圖。時至今日,node的開發環境已經很完善,從09年到現在這個技術已經走過了7年了,已經有很多書籍資料,國內的cnode社區很活躍。如果把技術比喻成股票的話,java,C#,PHP這些無疑是大盤白馬股,學這樣的技術風險小,不愁找不到工作。而node這樣的就像創業板股票,你也許認為這有很大的泡沫,認為新的公司不過是炒概念,但他就是在快速增長着。
源碼:http://files.cnblogs.com/files/stoneniqiu/Reading.rar
參考文章:
http://www.tuicool.com/articles/emeuie
http://www.2cto.com/kf/201207/142885.html
http://www.tuicool.com/articles/emeuie
https://github.com/expressjs/body-parser
書籍:《深入淺出nodejs》《Getting MEAN with Mongo, Express, Angular, and Node》