經過 express 咱們能夠輕鬆的構建一個 web 服務器 例如如下代碼就在 3000 端口建立了一個 web 服務器css
const express = require("express");
const app = express();
app.listen(3000, () => {
console.log("start");
});
複製代碼
在個人理解中 express 就是一個對一系列中間件調用的函數html
好比常見的處理 get 和 post 請求的方法都是中間件的調用node
當咱們 require express 的時候 本質上是導入了一個函數ios
能夠查看源碼 如下是入口文件 express.js 的截取代碼web
exports = module.exports = createApplication;
/** * Create an express application. * * @return {Function} * @api public */
function createApplication() {
var app = function (req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app },
});
// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app },
});
app.init();
return app;
}
複製代碼
express.js 導出了一個函數 createApplication 也就是咱們 require 導入的函數express
在這個函數中初始化了一個 app 的函數 擁有 req / res / next 等參數 並經過 minin 這個函數混入了不少屬性到 app 中json
具體的 API 可查閱 expressjs.com/en/4x/api.h…axios
傳遞給 express 的一個回調函數 中間件位於請求和響應之間 因此它能夠api
• 執行任何代碼服務器
• 更改請求和響應對象
• 結束請求響應週期 res.end
• 調用下一個中間件
咱們可使用 app.use 的方法在全局註冊中間件 這樣全部的路由都會匹配到這個中間件
也能夠在具體的路由中使用中間件 例如以下
const middlewareA = ((req,res,next)=>{
do something...
})
app.use(middlewareA)
app.get('/', middlewareA ,(req,res,next)=>{
do something...
})
複製代碼
const express = require("express");
const path = require("path");
const app = express();
// 經過/static 來訪問public文件夾
app.use("/static", express.static(path.join(__dirname, "public")));
app.listen(8080, () => {
console.log("靜態資源服務器部署成功");
});
複製代碼
get 請求的參數都在 url 中 咱們能夠經過 query 和 params 這兩種形式來獲取
const express = require("express");
const app = express();
app.get("/query", (req, res, next) => {
res.json(req.query);
});
app.get("/param/:id", (req, res, next) => {
res.json(req.params);
});
app.listen(3000, () => {
console.log("start");
});
複製代碼
post 請求的參數在 body 可是若是咱們直接打印 body 是看不到任何結果的
這是須要引入 body-parser 這個庫
這個庫和不一樣版本的 express 集成狀況以下
3.x 內置
4.x 分離
4.16 內置函數
因此若是是 4.x 4.16 之前的版本 咱們須要手動安裝這個第三方庫
const express = require("express");
const app = express();
// 老版本寫法 不推薦
// var bodyParser = require("body-parser");
// app.use(bodyParser.urlencoded({ extended: false }));
// app.use(bodyParser.json());
// 4.16之後的寫法 推薦
// extended false 表示使用node的內置模塊querystring來解析
// true 則表示使用第三方模塊qs來解析
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.post("/login", (req, res, next) => {
console.log(req.body);
});
app.listen(8000, () => {
console.log("start");
});
複製代碼
若是使用原生的方法實現文件上傳 後臺處理起來會很是麻煩 由於二進制流中不只有文件的信息 還有 header 的一些相關信息 咱們能夠打印一些相關的數據來看一下
const express = require("express");
const app = express();
app.post("/upload", (req, res, next) => {
console.log(req.headers);
let data = "";
req.on("data", (buffer) => {
data += buffer;
});
req.on("end", () => {
console.log(data.toString());
});
});
app.listen(8080, () => {
console.log("start~~~");
});
複製代碼
而後咱們訪問 localhost:8080/upload 並上傳一個文件 就能夠看到打印臺輸出了一段亂碼的二進制流和請求頭的 headers
headers 信息以下
{
"user-agent": "PostmanRuntime/7.13.0",
"accept": "*/*",
"cache-control": "no-cache",
"postman-token": "e48f538d-2988-4e39-8d50-80fdede0ed02",
"host": "localhost:8080",
"accept-encoding": "gzip, deflate",
"content-type": "multipart/form-data; boundary=--------------------------372446874075094600561084",
"content-length": "376074",
"connection": "keep-alive"
}
複製代碼
若是仔細對比 你就會發現二進制流中包含了 header 中的 content-type 中的 boundary 還有文件的 mime 類型等 若是不加處理的直接使用 fs 模塊將這個二進制流寫入到文件中 那麼最終文件確定沒法被正確解析
若是引入 multer 模塊 那麼文件上傳功能就會變得很簡單了
例如 咱們須要用戶上傳頭像 代碼以下
const express = require("express");
const multer = require("multer");
const path = require("path");
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// 第一個參數爲err的相關信息 node的回調函數都是錯誤優先的回調
// 第二個參數爲文件上傳的目的地
cb(null, "uploads");
},
filename: function (req, file, cb) {
// 一樣的 第一個參數爲錯誤信息 第二個參數爲文件名
cb(
null,
file.fieldname + "-" + Date.now() + path.extname(file.originalname)
);
},
});
const upload = multer({ storage: storage });
const app = express();
app.post("/upload", upload.single("avatar"), (req, res, next) => {
console.log(req.file);
});
app.listen(8080, () => {
console.log("start~~~");
});
複製代碼
上述方法演示的是單文件上傳 req.file 中存儲了文件的相關信息 以下
{
"fieldname": "avatar",
"originalname": "CHOU16.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "uploads",
"filename": "avatar-1616384964609.jpg",
"path": "uploads\\avatar-1616384964609.jpg",
"size": 375864
}
複製代碼
若是須要支持多文件上傳 例如還須要上傳用戶背景圖 代碼可改寫成以下
app.post(
"/fields",
upload.fields([
{ name: "avatar", macCount: 1 },
{ name: "bg", maxCount: 2 },
]),
(req, res, next) => {
console.log(req.files);
}
);
// {
// avatar: [
// {
// fieldname: 'avatar',
// originalname: 'CHOU1.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU1.jpg',
// path: 'uploads/CHOU1.jpg',
// size: 646567
// }
// ],
// bg: [
// {
// fieldname: 'bg',
// originalname: 'CHOU2.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU2.jpg',
// path: 'uploads/CHOU2.jpg',
// size: 398519
// }
// ]
// }
複製代碼
若是是須要多張背景圖上傳 同一個 field 則能夠用 array 的寫法 第二個參數爲最大可上傳的數量
若是超過了最大數量 multer 會返回 MulterError: Unexpected field
app.post("/array", upload.array("bg", 3), (req, res, next) => {
console.log(req.files);
});
// [
// {
// fieldname: 'bg',
// originalname: 'CHOU1.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU1.jpg',
// path: 'uploads/CHOU1.jpg',
// size: 646567
// },
// {
// fieldname: 'bg',
// originalname: 'CHOU2.jpg',
// encoding: '7bit',
// mimetype: 'image/jpeg',
// destination: 'uploads',
// filename: 'CHOU2.jpg',
// path: 'uploads/CHOU2.jpg',
// size: 398519
// }
// ]
複製代碼
若是將全部的請求處理都放在 index.js 中處理 那麼 index.js 的代碼就會變的很臃腫 這個時候咱們可使用路由來拆分咱們的代碼
例如項目中有一個用戶模塊 實現增刪改查的功能
咱們就能夠新建一個 user.js 文件
const express = require("express");
const router = express.Router();
router.get("/add", (req, res, next) => {
res.end("added");
});
router.post("/delete", (req, res, next) => {
res.end("deleted");
});
router.post("/update", (req, res, next) => {
res.end("updated");
});
router.post("/select", (req, res, next) => {
res.end("selected");
});
module.exports = router;
複製代碼
而後在 index.js 中導入這個路由
const express = require("express");
const userRouter = require("./user");
const app = express();
app.use("/user", userRouter);
app.listen(3000, () => {
console.log("start");
});
複製代碼
express 中的中間件都是同步代碼 即一箇中間件執行完畢纔會去執行另外一箇中間件中的代碼
例如咱們有以下功能要實現 在 ABC 三個中間件中 分別追加給 message 追加內容 而後在 A 中間件中輸出結果
const express = require("express");
const app = express();
const middlewareA = (req, res, next) => {
req.message = "";
req.message += "A";
next();
res.end(req.message);
};
const middlewareB = (req, res, next) => {
req.message += "B";
next();
};
const middlewareC = async (req, res, next) => {
req.message += "C";
next();
};
app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);
app.listen(8000, () => {
console.log(8000);
});
複製代碼
next 函數會在中間件棧中找到下一個中間件並執行 因此 middlewareA 中的 res.end 會在全部中間件都執行完畢後才執行 輸入 ABC
那麼 若是咱們在第三個中間件中用定時器來模擬異步操做 最終的結果會怎麼樣呢
將 middlewareC 改形成以下
const middlewareC = (req, res, next) => {
setTimeout(() => {
req.message += "C";
}, 0);
next();
};
複製代碼
經過訪問 8000 端口 咱們能夠看到此次最終的輸出變成了 AB 因而可知
中間件中的代碼都是同步調用的
而這 也是 express 面對異步場景下的一種無力 而 koa 則不同
koa 支持 async 和 await 的用法 這就意味着在 koa 中能夠拋去 express 中回調函數的寫法 用一種更優雅的方式來解決異步場景
與 express 不一樣的是 koa 導出的不是函數 而是一個名爲 Application 的對象
因此在使用上咱們只須要 new 一個實例便可 其餘用法和 Express 基本類似
const Koa = require("koa");
const app = new Koa();
app.listen(8080, () => {
console.log("Koa");
});
複製代碼
這裏咱們藉助第三方模塊 koa-router 由於 koa 自己很純淨 基本全部功能都要經過第三方插件來實現
新建一個 user.js 的路由模塊 Koa 將 express 中的 request 和 response 都合成到了上下文對象 context 中 簡寫爲 ctx
const Router = require("koa-router");
const userRouter = new Router({ prefix: "/user" });
userRouter.get("/home", (ctx, next) => {
ctx.body = "welcome~~";
});
userRouter.post("/login", (ctx, next) => {
ctx.body = "login";
});
module.exports = userRouter;
複製代碼
而後在 index 中引入 user.js
const Koa = require("koa");
const userRouter = require("./router");
const app = new Koa();
app.use(userRouter.routes());
app.listen(8080, () => {
console.log("Koa");
});
複製代碼
koa 中須要引入 koa-bodyparser 來解析 json 和 urlencoded
const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
const router = new Router();
app.use(bodyParser());
app.use(router.routes());
// 解析query
router.get("/query", (ctx, next) => {
console.log(ctx.request.query);
});
// 解析params
router.get("/params/:id", (ctx, next) => {
console.log(ctx.request.params);
});
// 解析urlencoded
router.post("/urlencoded", (ctx, next) => {
console.log(ctx.request.body);
});
// 解析json
router.post("/json", (ctx, next) => {
console.log(ctx.request.body);
});
app.listen(8080, () => {
console.log("start");
});
複製代碼
注意 koa-bodyparser 中間件須要最早被使用
從新回到 express 中的 demo 若是想在 koa 中處理異步操做就變的很是簡單了
const Koa = require("koa");
const axios = require("axios");
const app = new Koa();
const middlewareA = async (ctx, next) => {
ctx.msg = "";
ctx.msg += "A";
await next();
ctx.body = ctx.msg;
};
const middlewareB = async (ctx, next) => {
ctx.msg += "B";
await next();
};
const middlewareC = async (ctx) => {
const res = await axios.get("https://koa.bootcss.com/");
ctx.msg += res.data;
};
app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);
app.listen(8080, () => {
console.log("Koa");
});
複製代碼
洋蔥模型其實不是什麼高大尚的概念 經過下圖咱們不難發現
全部中間件都會被 request 訪問兩次 就像剝洋蔥同樣 這就是洋蔥模型
express 是完整和強大的,其中幫助咱們內置了很是多好用的功能;
koa 是簡潔和自由的,它只包含最核心的功能,並不會對咱們使用其餘中間件進行任何的限制。 甚至是在 app 中連最基本的 get、post 都沒有給咱們提供;咱們須要經過本身或者路由來判斷請求方式或者其餘功能
koa 中間件支持 async await