Express 筆記

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()
}

// Apply the middleware to all method at route '/'
app.use('/' ,validateUser)

// Apply the middleware to "GET" method at route '/'
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://www.example.com> HTTP/1.1
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()

// express.staic 也是內建的 middleware
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 安裝

npm install pug --save
const express = require('express')

const app = express()

// 告知 express, 我們要使用 pug 做 view engine
app.set('view engine', 'pug');

// 可以設定 view template 放在哪個檔案夾 , 預設會去讀同一層目錄的 views 檔案夾
// app.set('views', path.join(__dirname, 'views'))

app.get('/', (res, res) => {
res.render('index')
})

傳值到 view template 裡

const express = require('express')

const app = express()

// 告知 express, 我們要使用 pug 做 view engine
app.set('view engine', 'pug');

app.get('/', (res, res) => {
// 後面的參數其實都被放到 res.locals 裡面了
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) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)

// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
})

app.listen(8080)

我們平常在用 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');
// user 物件裡的 avatar 屬性是的 base64 圖片資料
res.send(req.user.avatar)
})

在做用戶驗證時,常用的驗證方式之一是 Session Based 的驗證方式,簡單來說就是當用戶登入成功後,我們在將一些驗證成功的資料放在入 cookie 讓之後的 client 的 request 都帶著這個 cookie 來讓 server 進行辨識,其中有兩種做法

  1. 將用戶資訊加密後放入 cookie 中, 可使用 cookie-session 套件來完成
  2. 在 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;
})

Pagination(分頁)

透過 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 第二個參數可以改下載檔案的名稱
res.download(path.join(__dirname, 'userStatements/BankStatement.png'))
})

express 其實只是把 header 改掉,然後呼叫 res.sendFile()

https://i.imgur.com/qtUrn4g.png

如果你只要將 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 第二個參數可以改下載檔案的名稱
res.download(
path.join(__dirname, 'userStatements/BankStatement.png'),
'BrianStatement',
(error) => {
if (error) {
// res.headersSent is a bool, true if headers are already sent
if (!res.headersSent) {
res.redirect('/download/error')
}
}
}
)
})

Router

express 提供,用來拆分 route 的 middleware

const express = require('express');
const { User } = require("../models");

const router = express.Router();

// Create user
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

Set header

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: {
// 1 million bytes
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: {
// 1 million bytes
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 文件:

錯誤處理

使用 apollo-studio 與 express-session 無法 set-cookie 問題 如何在 React Native 專案新增 iOS Widgets
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×