用Express寫一個hello world 程序。在合適的地方打開命令行窗口(使用的git bash),mkdir express-tut && cd express-tut && npm init -y, 初始化項目。再 touch server.js, 用於項目入口。因爲express 是第三方框架,咱們要先安裝它,npm install express -S, 最後code . (安裝vs code 的時候,加入到path, 因此code . 就能夠打開vscode)。server.js 代碼以下:css
const express = require("express"); const app = express(); app.use((request, response) => { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); app.listen(8080, () => { console.log(`server listen at 8080`) });
在git bash 中,輸入node server 開啓服務器,控制檯顯示‘server listen at 8080’ 表示開啓成功。瀏覽器中輸入localhost:8080 能夠看到Hello World, 簡單吧。項目雖然簡單,但也看到了express 項目開發時的核心,app.use() 方法,咱們在它的回調函數中來處理請求,app.use()最大的好處就是它能夠調用屢次,這樣,咱們就能夠把請求處理放到不一樣的app.use 的回調函數進行,一個函數只作一個功能,依次調用app.use() 方法,完成大的功能。好比,想在響應以前輸出請求日誌,直接在真正的響應以前添加一個app.use()方法,它接受的函數處理日誌,就能夠了html
const express = require("express"); const app = express(); // 記錄請求日誌 app.use((request, response, next) => { console.log("Incomes a " + request.method + " to " + request.url); next(); }); // 對請求作出響應 app.use((request, response) => { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); app.listen(8080, () => { console.log(`server listen at 8080`) });
記得重啓服務器(node server), 而後刷新瀏覽器,控制檯就能夠看到日誌了。依次調用兩個函數對http 請求進行處理,一個記錄日誌,一個負責響應,這就是Express 的中間件思想,不是把http請求放到一個大的函數中進行處理,而是把請求進行分解,放到每個函數中進行處理,node
一個函數只作一件事件,Express 則按照函數的書寫順序從上到下依次執行。這些處理函數稱爲中間件。git
再回頭看看記錄日誌的回調函數,你會發現多了一個next參數,當記錄完日誌後,調用了next() 方法。next() 表示,我這個中間件已經處理完了,能夠到下一個中間件了,若是有下一個中間件,下一個中間件會接着處理,這時到了響應請求的中間件。而 響應請求的回調中並無next 參數,這是由於服務器已經響應完客戶端請求了,無需下一步操做了,終止整個響應流程就能夠了,因此也就無需調用next().express
能夠看到中間件的主要功能就是攔截http 服務器提供的請求和響應對象,執行邏輯,或者結束響應,或者把它傳遞給下一個中間件組件,因此中間件都會接受兩個參數:請求對象(req),響應對象(res), 還有一個可選的參數next 函數, 調用next 函數能夠傳遞給下一個中間件組件. npm
本身創中間件的時候,只要按照這個模式建立就能夠了。不過,業界有一種更通用的方法,就是建立一個函數來返回中間件函數,這樣有利於建立可配置 的中間件。日誌記錄中間件修改以下:bootstrap
const express = require("express"); const app = express(); // 日誌記錄中間件 function logger (format) { return function(req,res, next) { console.log(req[format]); next(); } } // 日誌調用 app.use(logger('url')); // 對請求作出響應 app.use((request, response) => { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); app.listen(8080, () => { console.log(`server listen at 8080`) });
實際上,因爲中間件是一個個獨立的功能,爲此有不少第三方中間件可供使用, 好比日誌功能 中間件morgan,npm install morgan -S 安裝,而後在文件中替換掉咱們本身的函數數組
const express = require("express"); const logger = require("morgan"); // 引入morgan const app = express(); app.use(logger("short")) // 替換掉本身的函數 // 對請求作出響應 app.use((request, response) => { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); app.listen(8080, () => { console.log(`server listen at 8080`) });
Express 也內置了惟一的內置中間件static,它提供靜態文件服務,例如瀏覽器請求的圖片。在express-tut 文件夾下新建public文件夾,存放靜態資源,好比放一張圖片(如:flower.png) . static 中間件使用也很是簡單,它只接受一個參數,就是咱們靜態文件放置的目錄。瀏覽器
const express = require("express"); const logger = require("morgan"); // 引入morgan const path = require('path'); const app = express(); app.use(logger("short")) // 替換掉本身的函數 app.use(express.static(path.join(__dirname, 'public'))) // 靜態資源服務 // 對請求作出響應 app.use((request, response) => { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); app.listen(8080, () => { console.log(`server listen at 8080`) });
重啓服務器,在瀏覽器中輸入localhost:8080/flower.png的時候,網頁顯示一張圖片。但當咱們輸入localhost:8080 時候,Hello, world! 當有靜態文件服務的時候,若是咱們請求的靜態文件正好和服務器上的資源相匹配,它就會返回靜態資源,程序也不會繼續執行。 若是沒有相匹配的靜態資源,程序就會繼續執行.bash
在使用中間件的時候,必定要注意順序,好比把logger 中間件放到響應中間件的後面,它就不會輸出logger了。還有一種高級的用法,那就是中間件的掛載。就是在使用中間件的時候,在它前面添加一個路徑,只有在請求的url 中帶有此路徑,纔會調用這個中間件,那麼app.use 方法就須要接受兩個參數,第一個參數就是路徑,第二個參數就是中間件,如 app.use("/admin", admin) ,添加一個admin 中間件, 隨便在server.js中添加一下
app.use('/admin',(req, res) => { switch (req.url){ case '/': res.end("try user") break; case '/user' : res.end("hello user"); break; } });
當輸入localhost:8080/admin時,能夠看到try user ,代表它執行了admin中間件,且是第一個case, 再輸入localhost:8080/admin/user,能夠看到 hello user. 它仍是執行了admin 中間件,第二個case. 掛載,就是給這個中間件定義一個路徑,只有在訪問該路徑的時候,它才執行, 最經常使用的地方在於路由
Express路由
路由就是不一樣的url 響應不一樣內容,例如用戶點擊about , 咱們就要返回 about 頁面,用戶點擊home,咱們就要返回首頁。最簡單的實現方式是app.method(path, function) . method就是指的get, post 請求,path 指的就是/about, 後面的函數就是對這個請求的處理。如用戶點擊about, 服務器該怎麼返回呢? 它是一個get 請求,請求的是about, 咱們要返回about, 就能夠這麼寫,app.get('/about', function(req,res){res.end("about")}). 再寫幾個路由加深一下理解, 整個server.js 修改以下
let express = require('express') let app = express(); // 首頁路由 app.get("/", function(request, response) { response.end("Welcome to my homepage!"); }); // about 路由 app.get("/about", function(request, response) { response.end("Welcome to the about page!"); }); // weather 路由 app.get("/weather", function(request, response) { response.end("The current weather is NICE."); }); // 404 頁面 app.use(function(request, response) { response.statusCode = 404; response.end("404!"); }); app.listen(8080)
咱們在瀏覽器中輸入localhost:8080/about, 就看到 Welcome to the about page。
Express模版引擎
Express增長了對許多模板引擎的支持,如pug(jade), ejs等,能夠動態輸出html. 使用模版引擎有三個步驟,安裝,註冊,配置, 用ejs 演示一下
安裝很簡單 npm install ejs -S; 註冊是對express 不支的模版如handlebars 而言的,因爲Express 原生支持ejs, 因此不須要註冊;配置,就是告訴Express要使用哪一個模版引擎,模版文件放在什麼地方,以便Express在渲染的時候知道從哪裏去尋找模版。
在express-tut(根目錄) 中新建views文件夾,用於放置模版文件, 在views文件夾中新建一個模版文件,如index.ejs
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello, world!</title> </head> <body> <%= message %> </body> </html>
在server.js中配置模版引擎
app.set("view engine",'ejs'); // 設置 view engine, 使用ejs模版引擎 app.set("views", path.resolve(__dirname,'views')) // 設置views,模版放置的地方
動態渲染模版,輸出html。 要調用res.render()方法,render 方法,接受一個參數就是咱們要渲染的模版的名稱,還有一個可選的參數,就是向模版中輸入的數據 來替換模版中的變量。調用render方法的時候,express 就向views 文件夾中尋找對應的模版,這也是咱們 app.set("views",...) 的緣由。
app.get('/', function(req,res){ res.render('index',{
message:"this is a ejs view"
}) })
完成的server.js 以下,把其餘的日誌,static 都刪除了,在瀏覽器中輸入localhost:8080, 就看到 this is a ejs view。
const express = require("express"); const path = require('path'); const app = express(); app.set("view engine",'ejs'); // 設置 view engine, 使用ejs模版引擎 app.set("views", path.resolve(__dirname,'views')) // 設置views,模版放置的地方 // 對請求作出響應, 渲染模板 app.get('/', function(req,res){ res.render('index', { message:"this is a ejs view" }) }) app.listen(8080, () => { console.log(`server listen at 8080`) });
Express 的基本知識就差很少了,經過一個留言本實例來加深Express 的認識 ,再多加一箇中間件 body-parser,用來解析post請求 npm install body-parser --save,
server.js 文件以下:
// 引入各類模塊依賴 const express = require('express'); const logger = require('morgan'); const bodyParser = require('body-parser'); const path = require('path'); // 利用express 建立應用 const app = express(); // 設置模版引擎爲ejs app.set('views', path.join(__dirname, 'views')); app.set('view engine','ejs'); // 建立一個數組對象,保存用戶經過表單上傳的內容 var entries = []; // app.locals,它是一個對象,提供整個app應用所須要的數據,這時屬性entries就能夠用在模版中 app.locals.entries = entries; // 使用morgan 中間件記錄請求日誌 app.use(logger('dev')) // 利用body-parser 中間件獲取用戶上傳的數據,經過這個中間件,用戶上傳的數據都會附在req.body的屬性上。 app.use(bodyParser.urlencoded({extended: false})) //路由的設置 app.get('/', (req,res) => { res.render('index'); }) app.get('/new-entry', (req,res) => { res.render('new-entry'); }) app.post('/new-entry', (req,res) => { if(!req.body.title || !req.body.body) { res.status(400).send('Entris have a title and body') return; } entries.push({ title:req.body.title, content: req.body.body, published: new Date() }) res.redirect('/') }) // 當用戶訪問頁面,咱們的路由都不匹配時,提供回退404頁面 app.use((req,res) => { res.status(404).render('404') }) // 監聽3000端口,啓動服務 app.listen(3000, () => { console.log('server started on port 3000') })
這時須要提供幾個頁面,index.ejs, new-entries.ejs, 404.ejs, 因爲幾個頁面都有共同的footer和header, 因此咱們能夠把header 和footer 單獨提取出來,造成header.ejs 和footer.ejs,其它頁面直接引進就能夠了。在views 文件夾中新建這幾個頁面
header.ejs
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>留言板</title> <!--引入bootstrap 提供簡單的樣式--> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"> </head> <body class="container"> <h1> Express 留言板 <a href="/new-entry" class="btn btn-primary pull-right"> 寫一個留言 </a> </h1>
footer.ejs
</body> </html>
index.ejs 頁面主要顯示咱們留言的內容,而咱們全部的留言都存在的entries數組中,因此要用ejs中的循環對entries 進行遍歷,對內容進行輸出。因爲第一次進入頁面,咱們並無任何留言,因此要對entries 進行判斷,若是沒有內容,要讓用戶添加留言
<!--引入header--> <% include header %> <!--對entries進行判斷--> <% if (entries.length) { %> <!--有內容,進行循環遍歷,顯示內容--> <% entries.forEach(function(entry) { %> <div class="panel panel-default"> <div class="panel-heading"> <div class="text-muted pull-right"> <%= entry.published %> </div> <%= entry.title %> </div> <div class="panel-body"> <%= entry.content %> </div> </div> <% }) %> <!--沒有內容,提示用戶添加留言--> <% } else { %> 沒有留言內容! <a href="/new-entry">添加一個留言</a> <% } %> <!--引入footer--> <% include footer %>
因此當第一次進入頁面時顯示如下內容
當用戶點擊添加一個留言, 咱們要跳轉到添加留言的頁面,就是/new-entry頁面,這時添加new-entry頁面,它就是一個form 表單
<% include header %> <h2>書寫一個留言</h2> <form method="post" role="form"> <div class="form-group"> <label for="title">標題</label> <input type="text" class="form-control" id="title" name="title" placeholder="標題" required> </div> <div class="form-group"> <label for="content">內容</label> <textarea class="form-control" id="body" name="body" placeholder="內容" rows="3" required></textarea> </div> <div class="form-group"> <input type="submit" value="提交留交" class="btn btn-primary"> </div> </form> <% include footer %>
當提交訂單的時候,咱們並無給form 表單添加action, 表單內容提交到什麼地方? 原來表單中沒有指定action時,它會提交到當前頁面/new-entry, 正好對應server.js中的app.post(‘/new-entry’), 因此當提交訂單時,服務端會收到咱們傳過去的內容。
頁面內容展現以下:
最後提供一個404.ejs頁面
<% include header %> <h2>404! Page not found.</h2> <% include footer %>
好了,在命令行中輸入node server.js 開啓服務器,而後在瀏覽器中輸入localhost:8080體驗一下。
若是咱們想使用handlebars 做爲模板呢?也沒有問題,不過 須要安裝第三方模塊express-handlebars 進行支持,而且還要註冊模板引擎。npm install express express-handlebars -S, 安裝完成,在server.js 中引入express-handlebars.
const exphbs = require("express-handlebars")
調用app.engine() 進行註冊,內容以下
app.engine("handlebars", exphbs({ defaultLayout: "main",
layoutDir: app.get('views') + '/layouts',
partialsDir:[app.get('views') + '/partials'] }))
app.engine 接受兩個參數,一個是名字,這很好理解,註冊確定要給它起個名字,以便後面使用。第二個是handlebars 的配置,exphbs 是一個函數,接受對象做爲參數進行配置。default Layout:默認佈局文件名稱, layoutDir, 佈局文件所在的目錄,partialsDir局部文件所在的目錄,爲何這麼定義呢?須要瞭解一下handlebars 中的基本概念 : 佈局,局部, 還有視圖。
視圖(views): 就是咱們定義的任何的模板片斷
<div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">歡迎使用handlebars</h3> </div> </div>
佈局:也是一種模版,不過做用比較特殊,因此單獨列出來。想一下咱們的網站,主要分爲header ,main, footer 三個部分,一般header ,footer 部分是不變的,只有main 是常常改變的, 程序在運行的過程當中,只要動態的替換掉main 就能夠了。這時咱們就能夠定義一個文件,包含不變的header, footer 和 可變的main,這個文件就是佈局,因此在註冊handlebars的時候,咱們設置 defaultLayout 爲main,layoutDir文件夾
局部文件(partial):有些時候,有幾個頁面要共用相同的部分,如側邊欄這,咱們一般都會封裝爲組件,然而在handlebars 中,它們稱之爲局部文件partial,能夠知道局部文件也是代碼片斷。咱們在註冊模版引擎的時候,設置 partialsDir
好了,註冊成功了,就要告訴express來使用 handlerbars, 調用app.set 方法,第二個參數是咱們註冊的模板的名字,就是app.engine中第一個參數,他們要保證一致
app.set(「view engine」, 'handlebars' )
最後還要告訴express 咱們的模版文件放在什麼地方,以便express 去查找使用,仍是要調用app.set() 方法
app.set("views", __dirname+ "/views"); // 第一個參數views是複數,必定不忘記後面的還有一個s, 要否則會報 View is not a construction
如今可使用handlebars 了,不過根據handlebars 在註冊時的配置,還須要views 文件夾下面再建兩個文件夾 layouts 和partials, layouts 下面創建main.handlebars 做爲佈局 文件。注意,文件要以handlebars爲後綴名,由於咱們註冊的模板就是handlebars。簡單寫一個佈局文件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Express Handlebars 使用</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" > </head> <body> <div class="container"> <div class="row"> <div class="col-sm-8"> {{{ body }}} </div> <div class="col-sm-4"> {{> partial }} </div> </div> </div> </body>
他就是定義整個網頁的骨架,能夠看到裏面有兩個特殊的地方{{{body}}}, 咱們的view 就會渲染在{{{body}}}佔位符所在的地方。{{> partial }} 局部文件的名稱, 把局部文件partial.handlebar 放到這個地方。在views 文件夾中定義一個index.handlebars, 仍是上面的視圖吧
<div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">歡迎使用handlebars</h3> </div> </div>
在partials 文件下,建一個partial.handlebar 文件
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">側邊欄</h3> </div> </div>
定義的視圖,佈局,局部文件,咱們怎麼使用呢?這要用到render方法,去渲染模版。render 方法是定義在res 響應對象上的,因此咱們瀏覽器端發起一個請求,讓它輸出動態模版,因此咱們在 server.js 中定義一個路由,整個server.js以下
var express = require('express'), app = express(); // 引入express-handlebars var exphbs = require("express-handlebars") // 設置模板存放路徑 app.set("views", __dirname+ "/views") // 註冊handlebars模板引擎 var hbs = exphbs.create({ defaultLayout: "main", layoutDir: app.get('views') + '/layouts', partialsDir:[app.get('views') + '/partials'] }); app.engine('handlebars', hbs.engine); // 告訴express使用handlebars模板, // 第二個參數是咱們註冊的模板的名字,就是app.engine中第一個參數,他們要保證一致 app.set('view engine', 'handlebars' ) // render 渲染模板 app.get('/', function (req,res) { res.render('index') }) app.listen(8080);
render方法,接受一個參數,那就是要渲染的模版(視圖)的名字,當exrpess 執行render 的時候,它會從views 文件夾下找到咱們指定的文件,而後再找到佈局文件,替換掉裏面的{{{body}}},若是有partial, 它還會從particals 文件夾下找到局部文件,進行合併,形在一個完整的html文件進行輸出。這也就是咱們上面一系列設置文件夾的緣由。
咱們每次渲染一個視圖文件時,都會結合layout 佈局模版渲染, 有時咱們並不須要layout佈局模版,這時能夠在render 方法中進行設置 layout: false
app.use(function (req,res) { res.render('404', { layout: false }); })
在設置模版引擎的時候,咱們只指定了一個默認的佈局視圖,若是咱們帶想使用其餘視圖,怎麼辦? 調用render 方法的時候,指定layout,固然要確保這個layout文件在layout文件夾中。
app.get('/foo', function(req, res){ res.render('foo', { layout: 'microsite' }); })
那麼咱們在渲染的時候要給time傳遞參數, render 方法能夠接受第二個可選參數,它是一個對象, 就是咱們向模版中傳遞的數據,對象的屬性就是咱們在模版中定義的表達式,如time
res.render('index' , {
time: Date.now()
})
這時咱們發現,頁面中顯示的日期,但它是日期毫秒數,咱們想把他轉化成年月日的形式,這就要執行必定的邏輯操做,可是handlebars不支持在模版中使用邏輯的,這時要用到 helper 助手。它實際上是一個函數,對模版中的表達式執行邏輯操做, helper 在模版中使用以前要先註冊。這時有兩種方法,
一種是 在註冊handlebars 模版引擎的時候,直接給它配置helpers
var hbs = exphbs({ defaultLayout: "main", layoutDir: app.get('views') + '/layouts', partialsDir:[app.get('views') + '/partials'], // 增長helpers helpers: { timeFormate: function (time) { var dateTime = new Date(time) return dateTime.getFullYear() +"年"+ (dateTime.getMonth()+1)+ "月" + dateTime.getDay() +'日' } } });
一種是在調用render 方法的時候給它配置helpers
app.get('/', function (req,res) { res.render('index' , { time: Date.now(), //配置helpers helpers : { timeFormate: function (time) { var dateTime = new Date(time) return dateTime.getFullYear() +"年"+ (dateTime.getMonth()+1)+ "月" + dateTime.getDay() +'日' } } }) })
上面的第一種配置方式,能夠叫作全局配置,由於它在全部的模版文件中均可以使用,下面的一種則只能在 index模版中使用,能夠叫作局部配置。
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">
<!-- 增長 timeFormate helper -->
歡迎使用handlebars模板 <small>{{timeFormate time}}<small>
</h3>
</div>
</div>
express-handlebars還支持子目錄, 因此若是你有大量的局部文件,能夠將它 們組織在一塊兒。例如,你有一些社交媒體局部文件,能夠將它們放在views/ partials/social 目錄下面, 而後使用{{> social/facebook}}、{{> social/twitter}} 等來引入它們。