新聞中心
介紹
在本文中,我們將使用 Node.js 構(gòu)建一個(gè)簡(jiǎn)單的博客 API。 API代表“應(yīng)用程序編程接口”,它允許不同的軟件系統(tǒng)相互通信。 在這種情況下,我們的博客 API 將允許我們創(chuàng)建、讀取、更新和刪除博客文章,以及管理用戶身份驗(yàn)證。

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的南昌縣網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
為什么要使用 Node.js 構(gòu)建博客 API? Node.js 是一種流行的開(kāi)源運(yùn)行時(shí)環(huán)境,用于在瀏覽器外部執(zhí)行 JavaScript 代碼。 它擁有龐大而活躍的開(kāi)發(fā)人員社區(qū)以及豐富的庫(kù)和框架,可以輕松構(gòu)建可擴(kuò)展的高性能 Web 應(yīng)用程序。
先決條件
以下是我們需要的:
- Node.js: 您可以從官方網(wǎng)站下載最新版本的Node.js,并按照安裝說(shuō)明操作。 此操作還將安裝 Node.js 包管理器 (npm),我們將使用它來(lái)安裝其他包。
- MongoDB: 我們將使用 MongoDB 數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)數(shù)據(jù)。 您可以按照官方網(wǎng)站上的安裝說(shuō)明進(jìn)行操作。
- 代碼編輯器:您可以使用您喜歡的任何代碼編輯器,例如 Visual Studio Code、Sublime Text 或 Atom。
- 終端:我們將使用集成在 VS Code中的終端來(lái)運(yùn)行命令并與項(xiàng)目交互,但您可以使用您喜歡的終端。
- 基本了解 JavaScript、API、數(shù)據(jù)庫(kù)和其他 Web 開(kāi)發(fā)概念。 如果您是這些主題的新手,那么在深入學(xué)習(xí)本教程之前復(fù)習(xí)一下您的知識(shí)可能會(huì)有所幫助。
設(shè)置開(kāi)發(fā)環(huán)境
創(chuàng)建一個(gè)新項(xiàng)目
- 要?jiǎng)?chuàng)建一個(gè)新項(xiàng)目,請(qǐng)打開(kāi)您的終端并導(dǎo)航到您要?jiǎng)?chuàng)建項(xiàng)目的目錄。 然后,運(yùn)行以下命令:
npm init -y
- 這將在您的項(xiàng)目目錄中創(chuàng)建一個(gè) package.json 文件,用于管理項(xiàng)目的依賴項(xiàng)和腳本。 -y 表示接受所有默認(rèn)選項(xiàng),因此您不必手動(dòng)輸入選項(xiàng)。
- 接下來(lái),運(yùn)行以下命令來(lái)安裝這些依賴項(xiàng):
npm install --save express dotenv cors express-rate-limit helmet express-fileupload
文件結(jié)構(gòu)
現(xiàn)在我們已經(jīng)安裝了依賴項(xiàng),讓我們?yōu)轫?xiàng)目創(chuàng)建文件結(jié)構(gòu)。 在項(xiàng)目目錄中創(chuàng)建以下目錄和文件:
- /src: 這是我們項(xiàng)目的主目錄。 它將包含我們應(yīng)用程序的所有源代碼。
- /src/authentication: 該目錄將包含與用戶身份驗(yàn)證相關(guān)的代碼,例如密碼哈希加密和 JWT 生成。
- /src/config: 該目錄將包含我們應(yīng)用程序的配置文件,例如數(shù)據(jù)庫(kù)連接字符串和服務(wù)器端口號(hào)。
- /src/controllers: 該目錄將包含 API 控制器的代碼。 控制器處理傳入的請(qǐng)求并向客戶端發(fā)送響應(yīng)。
- /src/database: 此目錄將包含用于連接到數(shù)據(jù)庫(kù)并與之交互的代碼。
- /src/loggers: 該目錄將包含用于在我們的應(yīng)用程序中記錄日志的代碼。
- /src/models:該目錄將包含我們應(yīng)用程序的數(shù)據(jù)庫(kù)模型的代碼。 模型代表存儲(chǔ)在數(shù)據(jù)庫(kù)中的數(shù)據(jù),并提供與該數(shù)據(jù)交互的接口。
- /src/routes:該目錄將包含 API 路由的代碼。 路由定義我們 API 的端點(diǎn)(endpoint)并指定可用于訪問(wèn)它們的 HTTP 方法(例如 GET、POST)。
- /src/services: 該目錄將包含在整個(gè)應(yīng)用程序中使用的服務(wù)對(duì)象或?qū)嵱贸绦蚝瘮?shù)。
- /src/validators: 該目錄將包含用于驗(yàn)證輸入數(shù)據(jù)的代碼。
- app.js: 該文件將包含服務(wù)器的中間件和路由器。
- index.js: 這是我們應(yīng)用程序的入口點(diǎn)。 它將包含用于啟動(dòng)服務(wù)器和連接到數(shù)據(jù)庫(kù)的代碼。
- package.json: 此文件包含 API 的元數(shù)據(jù)。 啟動(dòng)腳本應(yīng)該是:“start”:“node index.js”。
設(shè)置環(huán)境變量
讓我們?cè)O(shè)置項(xiàng)目所需的環(huán)境變量。 隨著我們的構(gòu)建逐漸完成,為什么需要每個(gè)環(huán)境變量會(huì)變得逐漸清晰。
避免暴露 API ,因?yàn)檫@存在安全風(fēng)險(xiǎn)。 如果你使用版本控制,你可以設(shè)置一個(gè)配置文件,可以在不暴露任何秘密的情況下推送,或者你可以創(chuàng)建一個(gè) .env.example 只顯示變量名,沒(méi)有值。
在 /src/config 中創(chuàng)建 index.js 文件:
require("dotenv").config();
module.exports = {
PORT: process.env.PORT,
MONGODB_CONNECTION_URL: process.env.MONGODB_CONNECTION_URL,
JWT_SECRET: process.env.JWT_SECRET,
CLOUDINARY_URL: process.env.CLOUDINARY_URL,
};在根目錄中,創(chuàng)建一個(gè) .env 文件。 我們將在構(gòu)建過(guò)程中更新這些值:
PORT=5000
MONGODB_CONNECTION_URL=
JWT_SECRET=
CLOUDINARY_URL=
設(shè)置服務(wù)器
- 在 app.js 文件中,添加以下代碼:
const express = require("express");
const rateLimit = require("express-rate-limit");
const helmet = require("helmet");
const cors = require("cors");
const CONFIG = require("./src/config");
const app = express();
app.use(cors());
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: true,
message: "請(qǐng)求過(guò)多,請(qǐng)15分鐘后重試",
});
app.use(limiter);
app.use(helmet());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
fileUpload({
createParentPath: true,
useTempFiles: true,
tempFileDir: "/tmp/",
})
);
app.get("/", (req, res) => {
return res.json({ status: true });
});
// 404 error處理
app.use("*", (req, res) => {
return res.status(404).json({ message: "路由未找到" });
});
// Error處理
app.use(function (err, req, res, next) {
console.log(err.message);
res.status(err.status || 500).send("發(fā)生錯(cuò)誤");
});
module.exports = app;我們?cè)O(shè)置了以下中間件:
cors 允許跨源資源共享,這意味著我們的 API 可以從不同的域訪問(wèn)。
代碼中添加了限速中間件,可以限制用戶在一定時(shí)間內(nèi)允許發(fā)出的請(qǐng)求數(shù)量。 這有助于防止惡意用戶試圖用暴力請(qǐng)求攻擊服務(wù)器:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: true,
message: ""請(qǐng)求過(guò)多,請(qǐng)15分鐘后重試",
});
app.use(limiter);我們還有helmet中間件,它通過(guò)設(shè)置各種 HTTP 標(biāo)頭來(lái)幫助保護(hù) API。
然后,是以下中間件,它允許 API 處理請(qǐng)求正文中的 JSON 和 URL 編碼數(shù)據(jù):
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
最后,我們有 fileUpload 中間件,它允許 API 處理文件上傳:
app.use(fileUpload({
createParentPath: true,
useTempFiles: true,
tempFileDir: "/tmp/",
}));- 在index.js 文件中, 添加以下代碼:
const app = require("./app");
const CONFIG = require("./src/config");
app.listen(CONFIG.PORT, () {
console.log(`server listening on port ${CONFIG.PORT}`);
});前面我們?cè)O(shè)置了開(kāi)發(fā)環(huán)境和基本文件結(jié)構(gòu)。 現(xiàn)在,讓我們?cè)O(shè)計(jì) API 本身。 在節(jié)中,我們將討論以下主題:
- 決定 API 的路由和功能
- 為數(shù)據(jù)庫(kù)定義數(shù)據(jù)模型
- 實(shí)現(xiàn)數(shù)據(jù)模型
- 設(shè)置數(shù)據(jù)庫(kù)連接
決定路由和功能
設(shè)計(jì)API 的第一步是決定要包含的路由和功能。
概述一下我們博客的要求:
- 用戶應(yīng)該能夠注冊(cè)并登錄博客應(yīng)用程序。
- 博客文章可以處于兩種狀態(tài):草稿和已發(fā)布。
- 用戶能夠獲得已發(fā)表文章的列表,無(wú)論他是否登錄。
- 用戶應(yīng)該能夠獲得已發(fā)表的文章,無(wú)論他是否登錄。
- 登錄用戶應(yīng)該能夠創(chuàng)建文章。
- 創(chuàng)建文章時(shí),它應(yīng)該處于草稿狀態(tài)。
- 文章的作者應(yīng)該能夠?qū)⑽恼碌臓顟B(tài)更新為已發(fā)布。
- 文章的作者應(yīng)該能夠編輯處于草稿或已發(fā)布狀態(tài)的文章。
- 文章的作者應(yīng)該能夠刪除處于草稿或已發(fā)布狀態(tài)的文章。
- 文章的作者應(yīng)該能夠得到他們文章的列表。結(jié)果內(nèi)容應(yīng)該按狀態(tài)分頁(yè)和過(guò)濾。
- 創(chuàng)建的文章應(yīng)該有標(biāo)題、封面圖片、描述、標(biāo)簽、作者、時(shí)間戳、狀態(tài)、閱讀次數(shù)、閱讀時(shí)間和正文。
- 登錄和未登錄用戶都可以訪問(wèn)的文章列表端點(diǎn),內(nèi)容應(yīng)該分頁(yè)。
- 博客文章應(yīng)該可以按作者、標(biāo)題和標(biāo)簽進(jìn)行搜索。
- 博客文章還應(yīng)該可以按閱讀次數(shù)、閱讀時(shí)間和時(shí)間戳排序
- 請(qǐng)求單篇文章時(shí),API應(yīng)隨博客文章同時(shí)返回作者信息,博客閱讀次數(shù)加1。
考慮到上述要求,我們將定義路由如下:
博客路由:
- GET /blog: 檢索所有已發(fā)表文章的列表。
- GET /blog/:article_id:通過(guò) ID 檢索單篇文章。
作者路由: 我們希望只有經(jīng)過(guò)身份驗(yàn)證的用戶才能訪問(wèn)這些路由和所有 CRUD 操作。
- GET /author/blog: 檢索用戶創(chuàng)建的所有已發(fā)布文章的列表。
- POST /author/blog: 創(chuàng)建新文章。
- PATCH /author/blog/edit/:article_id: 通過(guò) ID 更新文章。
- PATCH /author/blog/edit/state/:article_id: 更新文章的狀態(tài)。
- DELETE /author/blog/:article_id: 按 ID 刪除文章。
認(rèn)證路由: 用于管理用戶身份驗(yàn)證。
- POST /auth/signup: 注冊(cè)一個(gè)新用戶。
- POST /auth/login: 登錄現(xiàn)有用戶。
定義數(shù)據(jù)模型
定義好路由后,我們就可以開(kāi)始考慮數(shù)據(jù)庫(kù)的數(shù)據(jù)模型了。 數(shù)據(jù)模型表示將存儲(chǔ)在數(shù)據(jù)庫(kù)中的數(shù)據(jù)以及這些數(shù)據(jù)之間的關(guān)系。 我們將使用 Mongoose 來(lái)定義我們的架構(gòu)。
我們將有兩個(gè)數(shù)據(jù)模型:Blog和User。
User
|
字段名 |
數(shù)據(jù)類型 |
約束 |
|
firstname |
String |
required |
|
lastname |
String |
required |
|
|
String |
required, unique, index |
|
password |
String |
required |
|
articles |
Array, [ObjectId] |
ref - Blog |
Blog
|
字段名 |
數(shù)據(jù)類型 |
約束 |
|
title |
String |
required, unique, index |
|
description |
String | |
|
tags |
Array, [String] | |
|
imageUrl |
String | |
|
author |
ObjectId |
ref - Users |
|
timestamp |
Date | |
|
state |
String |
required, enum: ['draft', 'published'], default:'draft' |
|
readCount |
Number |
default:0 |
|
readingTime |
String | |
|
body |
String |
required |
Mongoose 有一個(gè)名為 populate() 的方法,它允許您引用其他集合中的文檔。 populate() 將自動(dòng)用其他集合中的文檔替換文檔中的指定路徑。 User 模型將其 articles 字段設(shè)置為 ObjectId 的數(shù)組。 ref 選項(xiàng)告訴 Mongoose 在填充期間使用哪個(gè)模型,在本例中為Blog模型。 我們?cè)谶@里存儲(chǔ)的所有 _id 必須是博客模型中的文章 _id。 同樣,Blog 模型在其author字段中引用了 User 模型。
實(shí)現(xiàn)數(shù)據(jù)模型
- 在 /src/models目錄下, 創(chuàng)建一個(gè)名為 blog.model.js 的文件并設(shè)置 Blog 模型:
const mongoose = require("mongoose");
const uniqueValidator = require('mongoose-unique-validator');
const { Schema } = mongoose;
const BlogSchema = new Schema({
title: { type: String, required: true, unique: true, index: true },
description: String,
tags: [String],
author: { type: Schema.Types.ObjectId, ref: "Users" },
timestamp: Date,
imageUrl: String,
state: { type: String, enum: ["draft", "published"], default: "draft" },
readCount: { type: Number, default: 0 },
readingTime: String,
body: { type: String, required: true },
});
// 將 uniqueValidator 插件應(yīng)用于blog模型
BlogSchema.plugin(uniqueValidator);
const Blog = mongoose.model("Blog", BlogSchema);
module.exports = Blog;title字段被定義為必填的字符串,并且在集合中的所有文檔中必須是唯一的。 description 字段定義為字符串,tags 字段定義為字符串?dāng)?shù)組。 author字段定義為對(duì)用戶集合中文檔的引用,timestamp字段定義為日期。 imageUrl 字段定義為字符串,state 字段定義為具有一組允許值(“draft”或“published”)的字符串,readCount 字段定義為默認(rèn)值為 0 的數(shù)字。 readingTime 字段定義為字符串,body 字段定義為必填字符串。
mongoose-unique-validator 是一個(gè)插件,它為 Mongoose 模式中的唯一字段添加預(yù)保存驗(yàn)證。 如果唯一字段的值已存在于集合中,它將驗(yàn)證模型中的唯一選項(xiàng),并阻止插入文檔。
- 在 /src/models目錄下, 創(chuàng)建一個(gè)名為 user.model.js 的文件并設(shè)置User模型:
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const bcrypt = require("bcrypt");
const { Schema } = mongoose;
const UserModel = new Schema({
firstname: { type: String, required: true },
lastname: { type: String, required: true },
email: {
type: String,
required: true,
unique: true,
index: true,
},
password: { type: String, required: true },
articles: [{ type: Schema.Types.ObjectId, ref: "Blog" }],
});
// 將 uniqueValidator 插件應(yīng)用于用戶模型
UserModel.plugin(uniqueValidator);
UserModel.pre("save", async function (next) {
const user = this;
if (user.isModified("password") || user.isNew) {
const hash = await bcrypt.hash(this.password, 10);
this.password = hash;
} else {
return next();
}
});
const User = mongoose.model("Users", UserModel);
module.exports = User;firstname和lastname字段被定義為必填字符串,email字段被定義為必填字符串,并且在集合中的所有文檔中必須是唯一的。 password 字段定義為必填字符串,articles 字段定義為對(duì) Blog 集合中文檔的引用數(shù)組。
pre函數(shù)在特定方法執(zhí)行前執(zhí)行特定代碼。 在將用戶文檔保存到數(shù)據(jù)庫(kù)之前,此處的預(yù)保存函數(shù)使用 npm 模塊 bcrypt 對(duì)用戶密碼進(jìn)行哈希加密處理。
設(shè)置數(shù)據(jù)庫(kù)連接
現(xiàn)在我們已經(jīng)定義了路由和數(shù)據(jù)模型,是時(shí)候設(shè)置數(shù)據(jù)庫(kù)連接了。
- 設(shè)置您的 MongoDB 數(shù)據(jù)庫(kù)并將連接 url 保存在您的 .env 文件中。
- 運(yùn)行以下命令安裝 npm 包 mongoose:
npm install --save mongoose
- 在 /database 目錄中創(chuàng)建一個(gè)名為 db.js 的文件。 在 /database/db.js 中,使用 Mongoose 設(shè)置數(shù)據(jù)庫(kù)連接:
const mongoose = require('mongoose');
const connect = (url) {
mongoose.connect(url || 'mongodb://[localhost:27017](http://localhost:27017)')
mongoose.connection.on("connected", () {
console.log("Connected to MongoDB Successfully");
});
mongoose.connection.on("error", (err) {
console.log("An error occurred while connecting to MongoDB");
console.log(err);
});
}
module.exports = { connect };connect 函數(shù)有一個(gè)可選的 url 參數(shù),它指定要連接的數(shù)據(jù)庫(kù)的 URL。 如果未提供 URL,則默認(rèn)為“mongodb://localhost:27017”,這將連接到本機(jī)運(yùn)行的MongoDB數(shù)據(jù)庫(kù)實(shí)例的默認(rèn)端口 (27017) 。
- 在 /database 目錄下創(chuàng)建一個(gè) index.js 文件:
const database = require("./db");
module.exports = {
database,
};現(xiàn)在我們已經(jīng)建立了數(shù)據(jù)庫(kù)連接,在下文中,我們將深入探討兩個(gè)重要的概念——身份驗(yàn)證和數(shù)據(jù)驗(yàn)證。
讓我們從安裝所需的依賴項(xiàng)開(kāi)始:
npm install joi jsonwebtoken passport passport-jwt passport-local passport-local-mongoose
使用 Passport.js 進(jìn)行身份驗(yàn)證
為了在我們的 API 中對(duì)用戶進(jìn)行身份驗(yàn)證,我們將使用流行的 Passport.js 庫(kù)。 當(dāng)用戶登錄時(shí),我們將為用戶生成一個(gè) JWT 令牌。 該令牌將用于授權(quán)用戶對(duì) API 的請(qǐng)求。
在/authentication/passport.js 文件中,設(shè)置 JWT 策略:
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const { UserModel } = require("../models");
const JWTstrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;
passport.use(
new JWTstrategy(
{
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
},
async (token, done) => {
try {
return done(null, token.user);
} catch (error) {
done(error);
}
}
)
);JSON Web Token (JWT) 策略允許我們通過(guò)驗(yàn)證用戶的 JWT 來(lái)對(duì)用戶進(jìn)行身份驗(yàn)證。 要使用此策略,我們傳入一個(gè)密鑰(我們已將其存儲(chǔ)在 .env 文件中)和一個(gè)函數(shù)。
ExtractJWT.fromAuthHeaderAsBearerToken() 方法將從請(qǐng)求的授權(quán)標(biāo)頭中提取 JWT 作為 Bearer 令牌。 如果找到 JWT,它將被解碼并與 done 函數(shù)一起傳遞給該函數(shù)。 該函數(shù)將檢查 JWT 中的用戶對(duì)象以查看其是否有效。 如果有效,則調(diào)用帶有用戶對(duì)象的 done 函數(shù),表明用戶已通過(guò)身份驗(yàn)證,否則將調(diào)用 done 函數(shù)并出錯(cuò)。
對(duì)于 /signup 路由,我們將設(shè)置一個(gè)本地策略,它使用 passport-local 模塊通過(guò)用戶名(在本例中為他們的電子郵件地址)和密碼對(duì)用戶進(jìn)行身份驗(yàn)證。 當(dāng)用戶注冊(cè)時(shí),我們將在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)新的用戶文檔并將其返回給 Passport。
passport.use(
"signup",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.create({ ...req.body, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
- 登錄策略也將使用 passport-local 模塊。 當(dāng)用戶登錄時(shí),我們會(huì)在數(shù)據(jù)庫(kù)中搜索匹配的電子郵件地址,并驗(yàn)證該用戶是否存在于數(shù)據(jù)庫(kù)中。 如果登錄成功,我們將用戶對(duì)象返回給 Passport。
passport.use(
"login",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.findOne({ email });
if (!user) {
return done(null, false, { message: "User not found" });
}
return done(null, user, { message: "Logged in Successfully" });
} catch (error) {
return done(error);
}
}
)
);
我們?nèi)匀恍枰?yàn)證用戶的密碼。 在
/src/models/user.models.js 中,在 User 模型中創(chuàng)建一個(gè)名為 isValidPassword() 的方法。
UserModel.methods.isValidPassword = async function (password) {
const user = this;
const match = await bcrypt.compare(password, user.password);
return match;
};isValidPassword 將密碼作為參數(shù),并使用 bcrypt 的比較方法將其與用戶的散列密碼進(jìn)行比較。 該方法返回一個(gè)布爾值,指示密碼是否匹配。
在
src/authentication/passport.js中,在登錄策略中調(diào)用isValidPassword驗(yàn)證用戶密碼:
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {message: "Wrong Password" });
}
完整的文件應(yīng)如下所示:
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const { UserModel } = require("../models");
const JWTstrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;
passport.use(
new JWTstrategy(
{
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
},
async (token, done) => {
try {
return done(null, token.user);
} catch (error) {
done(error);
}
}
)
);
passport.use(
"signup",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.create({ ...req.body, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
passport.use(
"login",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.findOne({ email });
if (!user) {
return done(null, false, { message: "User not found" });
}
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, { message: "Wrong Password" });
}
return done(null, user, { message: "Logged in Successfully" });
} catch (error) {
return done(error);
}
}
)
);使用 Joi 驗(yàn)證輸入
為了驗(yàn)證用戶輸入,我們將使用 Joi 庫(kù)。 Joi 提供了一種簡(jiǎn)單但功能強(qiáng)大的方法來(lái)定義和驗(yàn)證 Node.js 應(yīng)用程序中的數(shù)據(jù)結(jié)構(gòu)。
在 /validators 目錄中,創(chuàng)建一個(gè)名為 author.validator.js 的文件:
const Joi = require("joi");
const newArticleValidationSchema = Joi.object({
title: Joi.string().trim().required(),
body: Joi.string().trim().required(),
description: Joi.string().trim(),
tags: Joi.string().trim(),
});
const updateArticleValidationSchema = Joi.object({
title: Joi.string().trim(),
body: Joi.string().trim(),
description: Joi.string().trim(),
tags: Joi.string().trim(),
state: Joi.string().trim(),
});
const newArticleValidationMW = async (req, res, next) => {
const article = req.body;
try {
await newArticleValidationSchema.validateAsync(article);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
const updateArticleValidationMW = async (req, res, next) => {
const article = req.body;
try {
await updateArticleValidationSchema.validateAsync(article);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
module.exports = {
newArticleValidationMW,
updateArticleValidationMW,
};我們導(dǎo)出兩個(gè)中間件函數(shù),newArticleValidationMW 和 updateArticleValidationMW。 newArticleValidationMW 使用
newArticleValidationSchema 來(lái)驗(yàn)證新建文章請(qǐng)求的請(qǐng)求正文是否包含所有必填字段(標(biāo)題、正文)以及所有提供的字段是否格式正確。 如果所有字段都有效,它將調(diào)用下一個(gè)函數(shù)以繼續(xù)。 updateArticleValidationMW,與前者類似,但它使用 updateArticleValidationSchema 來(lái)驗(yàn)證更新文章請(qǐng)求的請(qǐng)求。
這兩個(gè)函數(shù)都使用 Joi 庫(kù)提供的 validateAsync 方法來(lái)執(zhí)行驗(yàn)證。 此方法接受一個(gè)對(duì)象(請(qǐng)求主體)并返回一個(gè)承諾(promise),如果對(duì)象無(wú)效則拒絕該對(duì)象,如果對(duì)象有效則返回該對(duì)象。
在 /validators 目錄中,創(chuàng)建一個(gè)名為 user.validator.js 的文件:
const Joi = require("joi");
const validateUserMiddleware = async (req, res, next) => {
const user = req.body;
try {
await userValidator.validateAsync(user);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
const userValidator = Joi.object({
firstname: Joi.string().min(2).max(30).required(),
lastname: Joi.string().min(2).max(30).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
}),
password: Joi.string()
.pattern(new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})"))
.required(),
});
module.exports = validateUserMiddleware;- 在/validators/index.js文件中:
const userValidator = require("./user.validator");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("./author.validator");
module.exports = {
userValidator,
newArticleValidationMW,
updateArticleValidationMW,
};完成身份驗(yàn)證和驗(yàn)證后,我們就可以開(kāi)始構(gòu)建 API 路由和控制器了。
現(xiàn)在我們已經(jīng)完成了 API 設(shè)計(jì)、模型設(shè)置、數(shù)據(jù)庫(kù)連接建立和身份驗(yàn)證策略到位,是時(shí)候開(kāi)始構(gòu)建實(shí)際的 API 了。
- 為 API 實(shí)現(xiàn)路由。
- 編寫控制器來(lái)處理請(qǐng)求和發(fā)送響應(yīng)。
- 使用 Postman 測(cè)試 API。
實(shí)現(xiàn)路由
- 在 /routes 目錄中創(chuàng)建一個(gè)名為 blog.routes.js 的文件并編寫以下代碼:
const express = require("express");
const { blogController } = require("../controllers");
const blogRouter = express.Router();
blogRouter.get("/", blogController.getPublishedArticles);
blogRouter.get("/:articleId", blogController.getArticle);
module.exports = blogRouter;express.Router() 函數(shù)創(chuàng)建一個(gè)新的路由對(duì)象,可用于定義將用于處理對(duì)服務(wù)器的 HTTP 請(qǐng)求的路由。
定義的第一個(gè)路由是路由器根路徑中的 GET 路由。 此路由將處理對(duì)服務(wù)器的 HTTP GET 請(qǐng)求,并將使用 blogController 中的 getPublishedArticles 函數(shù)返回所有已發(fā)布文章的列表。
第二個(gè)路由是 GET 路由,在路徑中包含一個(gè)參數(shù) :articleId。 此路由將處理對(duì)路徑中具有特定文章 ID 的服務(wù)器的 HTTP GET 請(qǐng)求,并將使用 blogController 中的 getArticle 函數(shù)返回具有指定 ID 的文章。
在 /routes 目錄中創(chuàng)建一個(gè)名為 author.routes.js 的文件并編寫以下代碼:
const express = require("express");
const { authorController } = require("../controllers");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("../validators");
const authorRouter = express.Router();
// 創(chuàng)建新文章
authorRouter.post("/", newArticleValidationMW, authorController.createArticle);
// 改變狀態(tài)
authorRouter.patch(
"/edit/state/:articleId",
updateArticleValidationMW,
authorController.editState
);
// 編輯文章
authorRouter.patch(
"/edit/:articleId",
updateArticleValidationMW,
authorController.editArticle
);
// 刪除文章
authorRouter.delete("/delete/:articleId", authorController.deleteArticle);
// 根據(jù)創(chuàng)建的作者獲取其創(chuàng)建的文章
authorRouter.get("/", authorController.getArticlesByAuthor);
module.exports = authorRouter;在這里,
網(wǎng)頁(yè)名稱:使用 Node.js 構(gòu)建博客 API
轉(zhuǎn)載來(lái)源:http://fisionsoft.com.cn/article/coecdhp.html


咨詢
建站咨詢
