Express 入門

  用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}} 等來引入它們。

相關文章
相關標籤/搜索