Express Concept
Express is a bunch of middlewares works together
Expresss Basic Routing
const express = require('express'); const app = express();
app.get('/register', (req, res) => { const body = req.body; const isSucceed = auth(body); if (isSucceed) { res.send('Register Successfully'); } else { res.status(443).sene('Authentication fail, unable to register'); } });
|
Apply Middleware
const express = require('express')
const app = express()
function validateUser(req, res, next) { res.locals.validated = true; next() }
app.use('/' ,validateUser)
app.get('/', validateUser)
app.listen(3000)
|
Use the built-in middleware
express.json()
: 解析 conten-type 是 application/json
的 request
express.urlencoded()
: 解析 conten-type 是 application/x-www-form-urlencoded
的 request , 最常見 POST 提交數據的方式,瀏覽器的原生
表單,如果沒有設置 enctype 屬性,就會以默認以此方式提交數據
POST <http: Content-Type: application/x-www-form-urlencoded;charset=utf-8 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
|
通過使用以上內件的 middleware, 我們就可以在 res.body 得到前端 POST 過來的資料
const express = require('express')
const app = express()
app.use(express.json()) app.use(express.urlencoding())
app.post('/', (req, res, next) => { const data = res.body res.send('<h1>I got the data</h1>') })
|
Servering static content
express.static
: serving specified folder
const express = require('express')
const app = express()
app.use(express.static('public'))
|
使用 helmet 增加 server 安全性
express 文件
使用 Helemt
npm install --save helmet
|
Using View engine
熱門的 view engine 選項有
- pug
- handlebars:
npm i hbs
- mustache
- ejs
使用前要用 npm 安裝
const express = require('express')
const app = express()
app.set('view engine', 'pug');
app.get('/', (res, res) => { res.render('index') })
|
傳值到 view template 裡
const express = require('express')
const app = express()
app.set('view engine', 'pug');
app.get('/', (res, res) => { res.render('index', { title: 'Hey', message: 'Hello there!'}); })
|
html head title= title body h1= message
|
Handle cookies
setCookies
app.post('/proccess_login', (req, res, next) => { const userName = req.body.userName; const password = req.body.password; if (password === correctPassword) { res.cookie('userName', userName); res.redirect('/welcome') } else { res.redirect('/login?msg=fail') } })
|
parseCookies
Install cookie-parser
npm install cookie-parser
|
Apply cookie-parser as middleware
var express = require('express') var cookieParser = require('cookie-parser')
var app = express() app.use(cookieParser())
app.get('/', function (req, res) { console.log('Cookies: ', req.cookies)
console.log('Signed Cookies: ', req.signedCookies) })
app.listen(8080)
|
設定 Cookie 案例
我們平常在用 express 時都是回傳 JSON 格式,所以 express 會直接幫我們在 header 帶入
Content-Type: 'application/json'
|
但是如果我們今天要回傳圖片,我們就可以設定 Content-Type 為 image/jpg 格式
舉例,今天使用者要取的自己的大頭貼圖片
app.get('/user/me/avatar', auth, (req, res) => { res.set('Content-Type', 'image/jpg'); res.send(req.user.avatar) })
|
Handle Cookie and Session
在做用戶驗證時,常用的驗證方式之一是 Session Based 的驗證方式,簡單來說就是當用戶登入成功後,我們在將一些驗證成功的資料放在入 cookie 讓之後的 client 的 request 都帶著這個 cookie 來讓 server 進行辨識,其中有兩種做法
- 將用戶資訊加密後放入 cookie 中, 可使用 cookie-session 套件來完成
- 在 server 產生 session id,cookie 中只帶 session id, 用戶資料會存在另一個 Server 端的 DB (Redis), 每次 request 透過 session id 來辨識使用者,可以用 express-session 套件完成
Pass data from URL
Query string
假設前端 request url 為: https://{your_domain_name}/product?productId=abc13&productStatus=onSell
app.get('/product', (req, res, next) => { const query = req.query; const productId = query.productId; const productStatus = query.productStatus; })
|
在 redirect 時加入 params
app.post('/proccess_login', (req, res, next) => { const userName = req.body.userName; const password = req.body.password; if (password === correctPassword) { res.cookie('userName', userName); res.redirect('/welcome') } else { res.redirect('/login?msg=fail') } })
|
WildCards
假設前端 request url 為: https://{your_domain_name}/abc123/onSell
app.get('/product/:productId/:productStatus:', (req, res, next) => { const productId = req.params.productId; const productStatus = req.params.productStatus; })
|
透過 Query String 給 API 要 filter 的條件
- GET
/task?completed=true
只要取得完成的 task
- GET
/task?limit=10&skip=10
只拿 10 筆,並且跳過前十筆
router.ger('/tasks', auth, async (req, res) => { const query = req.query })
|
Download files
app.get('/statement', (req, res, next) => { res.download(path.join(__dirname, 'userStatements/BankStatement.png')) })
|
express 其實只是把 header 改掉,然後呼叫 res.sendFile()
如果你只要將 header 的 content-disposition 改為 attchment 可以改用 res.attachment()
app.get('/statement', (req, res, next) => { res.attachment(path.join(__dirname, 'userStatements/BankStatement.png')) })
|
檔案下載錯誤處裡, 通常錯誤發生時 response 可能已經回了,所以不能在 callback 裡面再重新送一次, 可以利用 res.headersSent
檢查 header 是否已送出
app.get('/statement', (req, res, next) => { res.download( path.join(__dirname, 'userStatements/BankStatement.png'), 'BrianStatement', (error) => { if (error) { if (!res.headersSent) { res.redirect('/download/error') } } } ) })
|
Router
express 提供,用來拆分 route 的 middleware
const express = require('express'); const { User } = require("../models");
const router = express.Router();
router.post('/users', async (req, res) => { const user = new User(req.body); try { const savedUser = await user.save() res.status(201).send(savedUser); } catch (e) { res.status(400).send(e); } })
|
向 express 註冊 Router
const express = require('express') const app = exprss()
const userRouter = require('./routes/userRouter')
app.use(userRouter)
|
Handle Http headers in Express
app.get('/', (req, res, next) => { const date = new Date(1969, 6, 20) res.set('Date', date) res.set('Context-Type', 'text/plain') })
|
Uploading files to Express with Multer
上傳圖片的格式是 multipart/form-data
可以用 multer 來 parse 檔案
假設我們有一個 form 可以上傳檔案
<html> <form method='post' action='formsub' enctype='multipart/form-data'> <input type='file' name="meme" /> </form> </html>
|
上傳單個檔案,檔案名稱需為 meme
const express = require('express') const multer = require('multer')
const app = express() const upload = multer({ dest: 'public/images/uploads', });
app.post('/formsub', upload.single('meme'),(req, res, next) => { const fileData = req.file; })
|
限制 Upload 大小、檔案類型
使用 limits
option 去限制上傳檔案大小
const app = express();
const multer = require('multer'); const upload = multer({ dest: 'images', limits: { fileSize: 1000000, } });
app.post('/upload', upload.single('upload'), (req, res) => { res.send(); });
|
使用 fileFilter
option
multer 提供三個參數給你用 fileFilter(req, file, cb)
第三個 cb
參數比較特別,
- 如果你接收這個檔案:
cb(null, true)
- 如果你拒絕接收這個檔案:
cb(null, false)
- 如果你想 throw 錯誤給 client:
cb(new Error('給 client 的錯誤'))
const app = express();
const multer = require('multer'); const upload = multer({ dest: 'images', limits: { fileSize: 1000000 }, fileFilter(req, file, cb) { if (!file.originalname.endsWith('.pdf')) { return cb(new Error('Please upload a PDF')) }
cb(null, true); } });
|
使用 storage
option 將設定為 memoryStorage
這個選項會存在記憶體中的選項轉為 Buffer
格式,可以透過 req.file.buffer
取得檔案 buffer
const storage = multer.memoryStorage() const upload = multer({ storage: storage })
const upload = multer({ fileFilter(req, file, cb) { if (!file.originalname.endsWith('.pdf')) { return cb(new Error('Please upload a PDF')) }
cb(null, true); }, storage });
router.post('/users/me/avatar', auth, upload.single('avatar'), async (req, res) => { req.user.avatar = req.file.buffer; await req.user.save(); res.send('ok'); })
|
Handling Express Errors
如果前面 middleware 丟出錯誤,我們可以新增 Error Handling Middleware
Error Handling Middleware, 與一般 middleware 的差別是
只有在 call signature 都是四個參數的的情況下,express 才會認定這是 error handling middleware,並且 call 這個 function
以下例子,我們先加上一個直接丟錯誤的 middleware 在 route handler 的前面, 後面加上再加上Error Handling Middleware
const errorMiddleware = (req, res, next) => { throw new Error('From my middleware'); }
app.post('/upload', errorMiddleware, (req, res) => { res.send('ok') }, (err, req, res, next) => { res.status(500).send({ error: error.message }) })
|
當實際執行可以發現當 error 被 throw 後 ,route handler 被跳過了,直接執行 Error Handling Middleware
其他細節和用法可以參考 express 文件:
錯誤處理