1、Express 框架簡介及初體驗
1.1、Express 框架是什么
Express 是一個屬於 Node 平台的 Web 應用開發框架,它提供了一系列的強大特性,幫助你創建各種 Web 應用。
我們可以使用 npm install express 命令進行下載安裝。
1.2、Express 框架特性
●提供了方便簡潔的路由定義方式
●對獲取 HTTP 請求參數進行了簡化處理
●對模板引擎支持程序高,方便渲染動態 HTML 頁面
●提供了中間件機制,有效控制 HTTP 請求
●擁有大量第三方中間件對功能進行擴展
1.3、原生 Node.js 與 Express 框架對比之路由
原始 node.js 代碼:
app.on('request', (req, res) => { // 獲取客戶端的請求路徑 let { pathname } = url.parse(req.url); // 對請求路徑進行判斷 不同的路徑地址響應不同的內容 if (pathname == '/' || pathname == '/index') { res.end('歡迎來到首頁'); } else if (pathname == '/list') { res.end('歡迎來到列表頁'); } else if (pathname == '/about') { res.end('歡迎來到關於我們頁'); } else { res.end('抱歉,您訪問的頁面出錯了'); } });
express 框架代碼:
// 當客戶端以 get 方式訪問 / 時 app.get('/', (req, res) => { // 對客戶端做出響應 res.send('Hello Express'); }); // 當客戶端以 post 方式訪問 /add 路由時 app.get('/add', (req, res) => { // 對客戶端做出響應 res.send('使用 post 方式請求了 /add 路由'); });
例子:
新建 framework 項目文件夾,並切換到命令行工具,輸入:npm install express 下載安裝框架。
新建 01.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.get('/', (req, res) => { // send() 向客戶端響應 // 1、send() 內部會檢測響應內容的類型 // 2、send() 會自動設置 http 狀態碼 // 3、send() 會幫我們自動設置響應的內容類型及編碼 res.send('hello express'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
切換到命令行工具,輸入:
node 01.js
打開瀏覽器,輸入:http://localhost:3000/ 可以看到:
在增加一個 /list:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.get('/', (req, res) => { // 1、send() 內部會檢測響應內容的類型 // 2、send() 會自動設置 http 狀態碼 // 3、send() 會幫我們自動設置響應的內容類型及編碼 res.send('hello express'); }) app.get('/list', (req, res) => { res.send({name: '張三', age: 20}); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
重新運行:node 01.js
刷新頁面可以看到:
2、中間件
2.1、什么是中間件
中間件就是一堆方法,可以接收客戶端發來的請求,可以對請求做出響應,也可以將請求繼續交給下一個中間件繼續處理。
中間件方法由 Express 提供,負責攔截請求,請求處理函數由開發人員提供,負責處理請求。
app.get('請求路徑', '處理函數') // 接收並處理get請求 app.post('請求路徑', '處理函數') // 接收並處理post請求
可以針對同一個請求設置多個中間件,對同一個請求進行多次處理。
app.get('/request', (req, res, next) => { req.name = '張三';
next() }) app.get('/request', (req, res) => { res.send(req.name); })
可以調用 next 方法將請求的控制權交給下一個中間件,直到遇到結束請求的中間件。
例子:新建 02.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.get('/request', (req, res, next) => { req.name = '張三'; next(); }); app.get('/request', (req, res) => { res.send(req.name); }); // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 02.js
回到瀏覽器輸入:http://localhost:3000/request ,可以看到:
2.2、app.use 中間件用法
app.use 匹配所有的請求方式,可以直接傳入請求處理函數,代表接收所有的請求。
app.use((req, res, next) => {
console.log(req.url);
next();
});
app.use 第一個參數也可以傳入請求地址,代表不論什么請求方式,只要是這個請求地址就接收這個秦秋。
app.use('/admn', (req, res, next) => {
console.log(req.url);
next();
});
例子:新建 03.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); // 接收所有請求的中間件 app.use((req, res, next) => { console.log('請求走了 app.use 中間件') next() }) // 當客戶端訪問 /request 請求的時候,才走當前的中間件 app.use('/request', (req, res, next) => { console.log('請求走了 app.use /request 中間件') next() }) app.get('/list', (req, res) => { res.send('/list'); }) app.get('/request', (req, res, next) => { req.name = '張三'; next(); }); app.get('/request', (req, res) => { res.send(req.name); }); // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具輸入:nodemon 03.js
在瀏覽器中輸入:http://localhost:3000/request
在瀏覽器中輸入:http://localhost:3000/list
2.3、中間件應用
1、路由保護,客戶端在訪問需要登錄的頁面時,可以先使用中間件判斷用戶登錄狀態,用戶如果未登錄,則攔截請求,直接響應,禁止用戶進入需要登錄的頁面。
例子:新建 04.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.use('/admin', (req, res, next) => { // 用戶沒有登錄 let isLogin = false; // 如果用戶登錄 if (isLogin) { // 讓請求繼續向下執行 next() } else { // 如果用戶沒有登錄,直接第客戶端做出響應 res.send('您還沒有登錄,不能訪問當前頁面'); } }) app.get('/admin', (req, res) => { res.send('您已經登錄,可以訪問當前頁面'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸人:nodemon 04.js
在瀏覽器中輸入:http://localhost:3000/admin ,可以看到:
把 isLogin 改為: let isLogin = true;,在重新刷新頁面,可以看到:
2、網站維護公告,在所有路由的最上面定義接收所有請求的中間件,直接為客戶端做出響應,網站正在維護中。
例子:繼續編輯 04.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); // 網站維護公告 app.use((req, res, next) => { res.send('當前網站正在維護中...'); }) app.use('/admin', (req, res, next) => { // 用戶沒有登錄 let isLogin = true; // 如果用戶登錄 if (isLogin) { // 讓請求繼續向下執行 next() } else { // 如果用戶沒有登錄,直接第客戶端做出響應 res.send('您還沒有登錄,不能訪問當前頁面'); } }) app.get('/admin', (req, res) => { res.send('您已經登錄,可以訪問當前頁面'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
回到瀏覽器中,隨便輸入那個請求地址,頁面都顯示:
3、自定義 404 頁面
中間是有順序的,如果從上到下都沒有匹配成功,那么就說明用戶訪問的這個請求路徑是不存在的。
例子:繼續編輯 04.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); // 網站維護公告 // app.use((req, res, next) => { // res.send('當前網站正在維護中...'); // }) app.use('/admin', (req, res, next) => { // 用戶沒有登錄 let isLogin = true; // 如果用戶登錄 if (isLogin) { // 讓請求繼續向下執行 next() } else { // 如果用戶沒有登錄,直接第客戶端做出響應 res.send('您還沒有登錄,不能訪問當前頁面'); } }) app.get('/admin', (req, res) => { res.send('您已經登錄,可以訪問當前頁面'); }) app.use((req, res, next) => { res.send('當前訪問的頁面不存在'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在瀏覽器中輸入:http://localhost:3000/add 可以看到:
不過在 network 里看到的狀態碼是 304
要想顯示為 404 的話,需要修改代碼:
app.use((req, res, next) => { // 為客戶端響應 404 狀態碼以及提示信息 // res.status(404) res.status(404).send('當前訪問的頁面不存在'); })
刷新頁面后可以看到:
2.4、錯誤處理中間件
在程序執行的過程中,不可避免的會出現一些無法預料的錯誤。比如文件讀取失敗,數據庫連接失敗等。
錯誤處理中間件是一個集中處理錯誤的地方。
代碼示例:
app.use((err, req, res, next) => { res.status(500).send('服務器發生未知錯誤'); })
例子:新建 05.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.get('/index', (req, res) => { throw new Error('程序發生了未知錯誤') }) // 錯誤處理中間件 app.use((err, req, res, next) => { res.status(500).send(err.message); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 05.js
在瀏覽器中輸入:http://localhost:3000/index ,可以看到:
注意:錯誤處理中間件只能捕捉到同步代碼執行出錯。如果異步代碼執行出錯,需要手動去觸發這個錯誤中間件。
當程序出現錯誤時,調用 next() 方法,並且將錯誤信息通過參數的形式傳遞給 next() 方法,即可觸發錯誤處理中間件。
示例代碼:
app.get('/', (req, res, next) => { fs.readFile('./file-dose-exist', (err, data) => { if (err) { next(err); } }) })
例子:修改 05.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); const fs = require('fs'); app.get('/index', (req, res, next) => { // throw new Error('程序發生了未知錯誤') // res.send('程序正常執行') fs.readFile('./demo.txt', 'utf8' ,(err, result) => { if (err != null) { next(err) } else { res.end(result) } }) }) // 錯誤處理中間件 app.use((err, req, res, next) => { res.status(500).send(err.message); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在瀏覽器中刷新頁面,可以看到:沒有找到文件
如果修改為當前目錄下有的文件:
fs.readFile('./01.js', 'utf8' ,(err, result) => {
刷新瀏覽器,可以看到:
2.5、捕獲錯誤
在 node.js 中,異步 API 的錯誤信息都是通過回調函數獲取的,支持 Promise 對象的異步 API 發生錯誤可以通過 catch() 方法捕獲。
異步函數執行如果發生錯誤要如何捕獲錯誤呢?
try catch 可以捕獲異步函數以及其他同步代碼在執行過程中發生的錯誤,但是不能捕獲其他類型的 API 發生的錯誤。
語法:
app.get('/', (req, res, netx) => { try { await User.find({name: '張三'}); } catch (ex) { next(ex); } });
例子:新建 06.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); const fs = require('fs'); const promisify = require('util').promisify; const readFile = promisify(fs.readFile); app.get('/index', async (req, res, next) => { try { await readFile('./aaa.js') } catch (ex) { next(ex); } }) // 錯誤處理中間件 app.use((err, req, res, next) => { res.status(500).send(err.message); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
刷新瀏覽器,可以看到:
3、Express 請求處理
3.1、構建模塊化路由
基礎代碼:
const express = require('express'); // 創建路由對象 const home = express.Router(); // 將路由和請求路徑進行匹配 app.use('/home', home); // 在 home 路由下繼續創建路由 home.get('/index', (req, res) => { // /home/index res.send('歡迎來到博客展示頁面'); });
例子:新建 07.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); // 創建路由對象 const home = express.Router(); // 為路由對象匹配請求路徑 app.use('/home', home); // 創建二級路由 home.get('/index', (req, res) => { res.send('歡迎來到博客展示頁面'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 07.js
在瀏覽器中輸入:http://localhost:3000/home/index ,可以看到:
構建模塊化路由代碼:
// home const home = express.Router(); home.get('/index', () => { res.send('歡迎來到博客展示頁面'); }); module.exports = home; // admin const admin = express.Router(); admin.get('/index', () => { res.send('歡迎來到博客管理頁面'); }); module.exports = admin; // app.js const home = require('./route/home.js'); const admin = require('./route/admin.js'); app.use('/home', home); app.use('/admin', admin);
例子:在項目根目錄下新建 route文件夾,並新建 home.js 和 admin.js 文件:
// home.js const express = require('express'); const home = express.Router(); home.get('/index', (req, res) => { res.send('歡迎來到博客展示首頁'); }); module.exports = home; // admin.js const express = require('express'); const admin = express.Router(); admin.get('/index', (req, res) => { res.send('歡迎來到博客管理首頁'); }); module.exports = admin;
新建 08.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express();
const home = require('./route/home'); const admin = require('./route/admin'); // 為路由對象匹配請求路徑 app.use('/home', home); app.use('/admin', admin); // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 08.js
打開瀏覽器輸入:http://localhost:3000/home/index 和 http://localhost:3000/admin/index ,可以看到:
3.2、GET參數的獲取
Express框架中使用 req.query 即可獲取 GET 參數,框架內部會將 GET 參數轉換為對象並返回。
示例代碼:
// 接收地址欄中問號后面的參數 // 例如: http://localhost:3000/?name=zhangsan&age=30 app.get('/', (req, res) => { console.log(req.query); // {"name": "zhangsan", "age": "30"} });
例子:新建 09.js 文件:
// 引入 express 框架 const express = require('express'); // 創建網站服務器 const app = express(); app.get('/index', (req, res) => { // 獲取 get 請求參數 res.send(req.query); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 09.js
在瀏覽器中輸入:http://localhost:3000/index?name=zhangsan&age=30 ,可以看到:
3.3、POST 參數的獲取
Express 中接收 post 請求參數需要借助第三方包 body-parser
下載安裝:npm install body-parser
示例代碼:
// 引入 body-parser 模塊 const bodyParser = require('body-parser'); // 配置 body-parser 模塊 app.use(bodyParser.urlencoded({ extended: false })); // 接收請求 app.post('/add', (req, res) => { // 接收請求參數 console.log(req.body); });
例子:
在命令行工具輸入:npm install body-parser
新建 10.js 文件:
// 引入 express 框架 const express = require('express'); // 引入 body-parser 模塊 const bodyParser = require('body-parser'); // 創建網站服務器 const app = express(); // 攔截所有的請求 // extended: false 方法內部使用 querystring 模塊處理請求參數的格式 // extended: true 方法內部使用第三方模塊 qs 處理請求參數的格式 app.use(bodyParser.urlencoded({ extended: false })) app.post('/add', (req, res) =>{ // 接收 post 請求參數 res.send(req.body) }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 10.js
我們通過表單的形式來發送 post 請求,新建 post.html 文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <form method="POST" action="http://localhost:3000/add"> <input name="username" type="text"> <input name="password" type="password"> <button type="submit">sbmit</button> </form> </body> </html>
右鍵點擊在瀏覽器中打開,然后隨便輸入一些信息,點擊提交,可以看到:
例子:新建 11.js 文件:
// 引入 express 框架 const express = require('express'); // 引入 body-parser 模塊 const bodyParser = require('body-parser'); // 創建網站服務器 const app = express(); app.use(fn ()) function fn() { return function (req, res, next) { console.log(req.method) next() } } app.get('/', (req, res) => { res.send('ok'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 11.js
在瀏覽器中輸入:http://localhost:3000/ ,可以看到:
這就證明了返回的函數 fn 被調用了。
這樣做的好處是:我們在調用 fn 函數的同時,向 fn 函數內部傳遞一些額外的參數;在請求處理函數內部,可以根據這個參數改變請求處理函數的行為。
修改 11.js 文件代碼:
// 引入 express 框架 const express = require('express'); // 引入 body-parser 模塊 const bodyParser = require('body-parser'); // 創建網站服務器 const app = express(); app.use(fn ({a: 1})) function fn(obj) { return function (req, res, next) { if ( obj.a == 1) { console.log(req.url) } else { console.log(req.method) } next() } } app.get('/', (req, res) => { res.send('ok'); }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
刷新瀏覽器,可以看到命令行工具中的:請求地址
如果把 app.use(fn ({a: 1})) 改為 app.use(fn ({a: 2}))
刷新瀏覽器,可以看到:請求方法
3.4、Express 路由參數
示例代碼:
app.get('/find/:id', (req, res) => { console.log(req.params); // {id: 123} });
// 瀏覽器中打開 localhost:3000/find/123
例子:新建 12.js 文件:
// 引入 express 框架 const express = require('express'); // 引入 body-parser 模塊 const bodyParser = require('body-parser'); // 創建網站服務器 const app = express(); app.get('/index/:id', (req, res) => { res.send(req.params) }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具中輸入:nodemon 12.js
在瀏覽器中輸入:http://localhost:3000/index/123 ,可以看到:
如果是多個參數的話:
app.get('/index/:id/:name/:age', (req, res) => {
res.send(req.params)
})
在瀏覽器中輸入:http://localhost:3000/index/123/zhangsan/20
3.5、靜態資源的處理
通過 Express 內置的 express.static 可以方便的托管靜態文件,例如 img、css、js 文件等。
app.use(express.static('public'));
現在 public 目錄下面的文件就可以訪問了。
例子:新建 13.js 文件:
// 引入 express 框架 const express = require('express'); const path = require('path'); // 創建網站服務器 const app = express(); app.use(express.static(path.join(__dirname, 'public'))); // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在命令行工具輸入:nodemon 13.js
在瀏覽器中可以輸入相應的地址,訪問靜態資源。比如:
http://localhost:3000/images/1.jpg
http://localhost:3000/css/base.css
http://localhost:3000/default.html
4、express-art-template 模板引擎
4.1、模板引擎
為了使 art-template 模板引擎能夠更好的和 Express 框架配合,模板引擎官方在原 art-template 模板引擎的基礎上封裝了 express-art-template。
同時安裝這兩個,使用 npm install art-template express-art-template 命令進行安裝。
語法:
// 當渲染后綴為 art 的模板時,使用 express-art-template app.engine('art', require('express-art-template')); // 設置模板存放目錄 app.set('views', path.join(__dirname, 'views')); // 渲染模板時不寫后綴,默認拼接 art 后綴 app.set('view engine', 'art'); app.get('/', (req, res) => { // 渲染模板 res.render('index'); })
例子:
在命令行工具中下載安裝:
npm install art-template express-art-template
新建 14.js 文件:
// 引入 express 框架 const express = require('express'); const path = require('path'); // 創建網站服務器 const app = express(); // 1、告訴 express 框架使用什么模板引擎,渲染什么后綴的模板文件 // 第1個參數是模板的后綴 // 第2個參數是使用的模板引擎 app.engine('art', require('express-art-template')); // 2、告訴 express 框架模板存放的位置是什么 app.set('views', path.join(__dirname, 'views')); // 3、告訴 express 框架模板的默認后綴是什么 app.set('view engine', 'art'); app.get('/index', (req, res) => { // 拼接模板路徑 // 拼接模板后綴、 // 那一個模板和那個數據進行拼接 // 將拼接的結果響應給客戶端 res.render('index', { msg: 'message' }) }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在項目根目錄下新建 views 文件夾,創建 index.art 文件:
{{ msg }}
在命令行工具中輸入:nodemon 14.js
在瀏覽器輸入:http://localhost:3000/index ,可以看到:
修改添加如下代碼:
// 引入 express 框架 const express = require('express'); const path = require('path'); // 創建網站服務器 const app = express(); // 1、告訴 express 框架使用什么模板引擎,渲染什么后綴的模板文件 // 第1個參數是模板的后綴 // 第2個參數是使用的模板引擎 app.engine('art', require('express-art-template')); // 2、告訴 express 框架模板存放的位置是什么 app.set('views', path.join(__dirname, 'views')); // 3、告訴 express 框架模板的默認后綴是什么 app.set('view engine', 'art'); app.get('/index', (req, res) => { // 拼接模板路徑 // 拼接模板后綴、 // 那一個模板和那個數據進行拼接 // 將拼接的結果響應給客戶端 res.render('index', { msg: 'message' }) }) app.get('/list', (req, res) => { res.render('list', { msg: 'list page' }) }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
在路籃球中輸入:http://localhost:3000/list ,可以看到:
4.2、app.locals 對象
將變量設置到 app.locals 對象下面,這個數據在所有的模板中都可以獲取到。
示例代碼:
app.locals.users = [{ name: '張三', age: 20 },{ name: '李四', age: 20 }]
例子:新建 15.js 文件:
// 引入 express 框架 const express = require('express'); const path = require('path'); // 創建網站服務器 const app = express(); // 模板配置 app.engine('art', require('express-art-template')); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'art'); app.locals.users = [{ name: '張三', age: 20 },{ name: '李四', age: 30 }] app.get('/index', (req, res) => { res.render('index', { msg: '首頁' }) }) app.get('/list', (req, res) => { res.render('list', { msg: '列表頁' }) }) // 監聽端口 app.listen(3000); console.log('網站服務器啟動成功');
打開 views 目錄下的 index.art 和 list.art 文件,同時添加:
{{ msg }} <ul> {{each users}} <li> {{$value.name}} {{$value.age}} </li> {{/each}} </ul>
在命令行工具中輸入:nodemon 15.js
在瀏覽器輸入:http://localhost:3000/index 和 http://localhost:3000/list ,可以看到:
這就證明了 app.locals 這個對象下面的屬性,我們在所有的模板中都可以拿到。所以我們可以把項目中一些公共數據,添加到 app.locals 這個對象下面。
4.3、路由重定向
res.redirect('/admin/user');
項目實例:博客項目