Node+Express搭建個人博客(純后端)-淺析Express腳手架(三)

既然我們的個人博客項目使用的是Express框架鸥滨,那我們在項目初始化之前鸥咖,先從【初始化一個Express項目】說起,分析下默認的Express腳手架招狸。

本文篇幅稍稍有點長敬拓,超出了計劃,請耐心閱讀【手動捂臉】裙戏。

初始化一個Express項目

Express框架的官方文檔中乘凸,是通過生成器工具express-generator快速生成了一個Express應(yīng)用,相關(guān)步驟如下:

安裝Express生成器

npm install -g express-generator

初始化一個Express應(yīng)用

express express-app

如此便生成了一個名為express-app的Express應(yīng)用累榜。

目錄結(jié)構(gòu)概覽

首先來看一下這個Express應(yīng)用的目錄結(jié)構(gòu)营勤,目錄結(jié)構(gòu)如下:

express-app
│  app.js
│  package.json
│  
├─bin
│      www
│      
├─public
│  ├─images
│  ├─javascripts
│  └─stylesheets
│          style.css
│          
├─routes
│      index.js
│      users.js
│      
└─views
        error.jade
        index.jade
        layout.jade

目錄結(jié)構(gòu)簡介

  • app.js應(yīng)用的初始化文件,包括引入應(yīng)用程序的基礎(chǔ)依賴項壹罚、設(shè)置視圖即view的引擎目錄以及模板葛作、設(shè)置靜態(tài)資源路徑、配置通用的中間件猖凛、引入路由和一些錯誤處理中間件等赂蠢。
  • package.json應(yīng)用的配置文件,文件內(nèi)包含程序的基礎(chǔ)信息辨泳、啟動腳本和依賴包等虱岂。
  • bin/www應(yīng)用的啟動文件玖院,文件內(nèi)包含引用要啟動的應(yīng)用、設(shè)置應(yīng)用監(jiān)聽的端口和啟動http服務(wù)等第岖。
  • public/**應(yīng)用的靜態(tài)資源文件目錄司恳,該目錄下的文件資源不需要經(jīng)過文件映射就可以直接訪問。
  • routes/**應(yīng)用的路由文件绍傲,這些路由文件中設(shè)置的接口最終會以指定的HTTP請求方式暴露給用戶,并在用戶請求之后將結(jié)果返回耍共。
  • views應(yīng)用的視圖文件烫饼,在app.js中設(shè)置好視圖引擎和模板之后,該目錄即為應(yīng)用視圖的根目錄试读,然后路由文件就會根據(jù)app.js中的設(shè)置加載并渲染該目錄下的視圖文件杠纵。

應(yīng)用啟動

首先,先要安裝應(yīng)用的依賴包钩骇。

npm install

然后比藻,啟動程序。

node bin/www

查看bin/wwww文件倘屹,拷打應(yīng)用默認監(jiān)聽的端口是3000银亲,然后訪問localhost:3000,看到如下界面:

image

看到上面的頁面,說明我們的應(yīng)用啟動成功纽匙,并且成功地訪問到了視圖引擎目錄view中的頁面务蝠。

文件詳解

app.js文件詳解

以下是初始化的Express應(yīng)用中的已經(jīng)做了一些注釋說明后的app.js文件中的代碼:

// 引入依賴包
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

// 引入路由文件
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

// 創(chuàng)建應(yīng)用實例
var app = express();

// 設(shè)置視圖目錄和模板引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// 以下皆為注冊中間件
// 內(nèi)置中間件
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 路由中間件
app.use('/', indexRouter);
app.use('/users', usersRouter);

// 404錯誤處理中間件
app.use(function(req, res, next) {
  next(createError(404));
});

// 錯誤處理中間件
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');
});

// 導(dǎo)出app實例對象
module.exports = app;

根據(jù)代碼中的注釋,我們可以看到烛缔,app.js的作用主要是初始化Express應(yīng)用的一些設(shè)置馏段,包括引入依賴包、引入路由文件践瓷、注冊各類中間件的一些操作院喜。

www文件詳解

以下是添加了注釋后的應(yīng)用啟動文件bin/www中的代碼:

#!/usr/bin/env node

//  引入依賴
var app = require('../app');
var debug = require('debug')('express:server');
var http = require('http');

// 設(shè)置應(yīng)用監(jiān)聽的端口
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

// 創(chuàng)建http服務(wù)器
var server = http.createServer(app);

// 監(jiān)聽端口
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

// 格式化應(yīng)用監(jiān)聽的端口
function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

// http的錯誤監(jiān)聽函數(shù)
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

// http端口監(jiān)聽函數(shù)
function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

bin/wwww文件的內(nèi)容就很簡單了,就是創(chuàng)建HTTP服務(wù)晕翠,監(jiān)聽端口和錯誤喷舀。

router/**詳解

router/**包含了路由文件,每個路由文件中包含了不同HTTP請求對用的路由以及邏輯處理函數(shù)崖面,此處以router/index.js文件為例元咙,以下是index.js文件的代碼:

// 引入依賴包
var express = require('express');

// 創(chuàng)建路由對象
var router = express.Router();

// 獲取首頁
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

路由文件主要相應(yīng)相應(yīng)的HTTP請求,然后執(zhí)行函數(shù)進行相應(yīng)的邏輯處理巫员,以上代碼庶香,是對GET請求的響應(yīng),然后渲染view目錄下的index.jade頁面(在app.js中設(shè)置了視圖目錄和模板引擎)简识,同時傳遞數(shù)據(jù){title: 'Express'}index.jade頁面赶掖。

view/**詳解

view/**主要存放應(yīng)用的頁面感猛,以view/index.jade為例,代碼如下:

extends layout

block content
  h1= title
  p Welcome to #{title}

頁面文件主要是接收路由文件傳遞的數(shù)據(jù)奢赂,然后生成渲染后的頁面給客戶端陪白,在上一步,傳遞了數(shù)據(jù){title: 'Express'}index.jade頁面膳灶,所以真實的頁面應(yīng)該是如下的代碼:

extends layout

block content
  h1= 'Express'
  p Welcome to Express

然后客戶端就看到了如下的頁面:


image

改進

缺點分析

在實際開發(fā)中咱士,我們項目的路由文件的數(shù)目會因項目復(fù)雜度而不同,當(dāng)項目足夠復(fù)雜時轧钓,我們需要引入的路由文件和需要注冊的路由就會更多序厉,同時除了應(yīng)用本身的404和錯誤處理中間件之外,還可能根據(jù)實際需求新增我們自定義的中間件毕箍,如果這些初始化的代碼都放置于app.js中弛房,無疑,app.js文件會越來越大越來越臃腫而柑,這并不是一個很好的選擇文捶。同時路由和路由對應(yīng)的處理函數(shù)在一個文件中,這樣的處理不夠優(yōu)雅媒咳,不便于維護粹排,所以進行路由分離顯得很有必要性。當(dāng)然伟葫,這個應(yīng)用只是生成器工具初始化的一個簡單腳手架恨搓,并不是最終的樣子。

MVC框架簡介

MVC框架是一個經(jīng)典的架構(gòu)模式筏养,其中MVC分別指的是:

  • M: Model(模型斧抱,包含數(shù)據(jù)和數(shù)據(jù)處理方法)
  • V: View(視圖,提供視圖渐溶,即頁面)
  • Controller: (控制層辉浦,負責(zé)邏輯處理)

對應(yīng)的,在Node中的MVC框架茎辐,處理流程應(yīng)該是以下步驟:

  1. 服務(wù)端收到客戶端的請求
  2. 路由層開始處理服務(wù)端接收到的請求宪郊,匹配相應(yīng)的路由
  3. 匹配到相應(yīng)的路由之后調(diào)用對應(yīng)的controller(即對應(yīng)的邏輯處理函數(shù))
  4. controller接收到請求之后,向model層取用數(shù)據(jù)
  5. model層接收到controller層取用數(shù)據(jù)的請求之后拖陆,將數(shù)據(jù)返回給controller
  6. controller層收到數(shù)據(jù)之后進行相應(yīng)的邏輯處理之后弛槐,將數(shù)據(jù)返回給view
  7. view層收到controller層返回的數(shù)據(jù)之后,根據(jù)相應(yīng)的視圖模板和數(shù)據(jù)組裝之后依啰,返回一個渲染過的頁面
  8. 服務(wù)端將上一步的結(jié)果返回給客戶端

注:在以上的步驟中乎串,可能會存在執(zhí)行中間件的情況。

在對Express框架初始化的應(yīng)用缺點和MVC有了簡單了解之后速警,接下來就是根據(jù)以上兩步的分析改進這個簡單的腳手架應(yīng)用了叹誉。

啟動文件和初始化文件合二為一

Express的官方文檔中關(guān)于app.listen()方法的介紹中鸯两,提到:

Binds and listens for connections on the specified host and port. This method is identical to Node’s http.Server.listen().

所以我們完全可以使用app.listen()方法替代bin/www中的代碼邏輯,一行代碼替代一個應(yīng)用的啟動文件长豁,何樂而不為呢钧唐。

既然要合二為一,我們將程序的啟動文件和初始化文件都合并到app.js中匠襟,這樣的話钝侠,就不要要導(dǎo)出app對象給其他文件引入使用了,所以酸舍,首先刪除app.js文件中的module.exports = app;,然后机错,添加一行代碼即可:

app.listen(3000)

修改app.js之后,訪問localhost:3000,依然可以正常訪問到上面的頁面父腕,說明修改無誤。

抽離路由層

參照MVC框架青瀑,路由層的工作是根據(jù)匹配客戶端的請求路徑匹配相應(yīng)的路由然后調(diào)用相應(yīng)的controller璧亮,以下代碼為示例模板:

// 訪問首頁
app.get('/', IndexController.index)

在該小節(jié),我們僅僅抽離出路由層斥难,一步一步的來枝嘶。

首先,在項目根目錄下創(chuàng)建route.js文件,該文件中的代碼如下:

module.exports = function (app) {
  app.get('/', function(req, res, next) {
    console.log('new index')
    res.render('index', { title: 'Express' });
  })
}

然后在app.js中添加如下代碼:

var routes = require('./routes')
routes(app)

注:routes(app)需要在創(chuàng)建應(yīng)用實例之后哑诊,即var app = express()之后群扶。

同時,刪除app.js中的// app.use('/', indexRouter)镀裤。

最后竞阐,訪問localhost:3000,依然可以正常訪問到上面的頁面,并且在控制臺看到輸出的new index,說明修改無誤暑劝。

抽離controller層

首先骆莹,在根目錄下創(chuàng)建controller/index.js,在index.js中編寫如下代碼:

module.exports = {
  index (req, res, next) {
    console.log('controller')
    res.render('index', { title: 'Express' });
  }
}

然后担猛,將routes.js修改為如下代碼:

var IndexController = require('./controller/index')
module.exports = function (app) {
  app.get('/', IndexController.index)
}

最后,訪問localhost:3000,依然可以正常訪問到上面的頁面,并且在控制臺看到輸出的controller,說明修改無誤聊记。

抽離middleware層

首先浩蓉,在根目錄下創(chuàng)建middlewares/not-find.js,在文件中編寫以下代碼:

var createError = require('http-errors');
module.exports = function(req, res, next) {
  next(createError(404));
}

然后在app.js中蒸走,引入相關(guān)中間件:

var notFind = require('./middlewares/not-find')

然后修改404錯誤處理中間件為:

// 404錯誤處理中間件
app.use(notFind);

最后仇奶,訪問localhost:3000/asasasasasa,這是一個不存在的路徑,應(yīng)該會出現(xiàn)404頁面载碌,發(fā)現(xiàn)可以正常訪問到404頁面猜嘱,說明修改無誤衅枫。

其他自定義中間件的處理,同理朗伶。

小結(jié)

至此弦撩,我們對生成器工具express-generator生成的Express應(yīng)用的改造完畢,并且接下來初始化自己的應(yīng)用可以不再使用express-generator论皆,可以根據(jù)自己的需求益楼,參照以上修改,進行項目初始化搭建点晴。

下面附上項目的github地址:

項目地址

我的個人博客:

毛浩先生的個人博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末感凤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子粒督,更是在濱河造成了極大的恐慌陪竿,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屠橄,死亡現(xiàn)場離奇詭異族跛,居然都是意外死亡,警方通過查閱死者的電腦和手機锐墙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門礁哄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溪北,你說我怎么就攤上這事桐绒。” “怎么了之拨?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵茉继,是天一觀的道長。 經(jīng)常有香客問我蚀乔,道長馒疹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任乙墙,我火速辦了婚禮颖变,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘听想。我一直安慰自己腥刹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布汉买。 她就那樣靜靜地躺著衔峰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垫卤,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天威彰,我揣著相機與錄音,去河邊找鬼穴肘。 笑死歇盼,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的评抚。 我是一名探鬼主播豹缀,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慨代!你這毒婦竟也來了邢笙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侍匙,失蹤者是張志新(化名)和其女友劉穎氮惯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體想暗,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡筐骇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了江滨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厌均,死狀恐怖唬滑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棺弊,我是刑警寧澤晶密,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站模她,受9級特大地震影響稻艰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜侈净,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一尊勿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畜侦,春花似錦元扔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春擅羞,著一層夾襖步出監(jiān)牢的瞬間尸变,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工减俏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留召烂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓垄懂,卻偏偏與公主長得像骑晶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子草慧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,116評論 25 707
  • 用兩張圖告訴你桶蛔,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,723評論 2 59
  • 引用:https://github.com/WangZhechao/expross 1.簡介 這篇文章是分析exp...
    宮若石閱讀 3,082評論 1 8
  • 再逢盛夏漫谷,好久不見的盛夏仔雷。 不知怎么,雨一直下舔示。 陰沉沉碟婆, 若是可見些許微光,便感覺欣喜惕稻。 撐一把雨傘竖共,走一條老街...
    _她一直笑_閱讀 328評論 3 4
  • 早上8點到達長沙南,吃個早餐就跑去岳麓找喻佳珞俺祠。 休息聊了一會天說出去玩玩公给,先去了岳麓公園,后又去了梅溪湖蜘渣,走著走...
    嵐風(fēng)的葉子閱讀 386評論 0 0