在本文中,我们将使用 Node.js 构建一个简单的博客 API。 API代表“应用程序编程接口”,它允许不同的软件系统相互通信。 在这种情况下,我们的博客 API 将允许我们创建、读取、更新和删除博客文章,以及管理用户身份验证。
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站建设、成都网站设计、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的南昌县网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
为什么要使用 Node.js 构建博客 API? Node.js 是一种流行的开源运行时环境,用于在浏览器外部执行 JavaScript 代码。 它拥有庞大而活跃的开发人员社区以及丰富的库和框架,可以轻松构建可扩展的高性能 Web 应用程序。
以下是我们需要的:
npm init -y
npm install --save express dotenv cors express-rate-limit helmet express-fileupload
现在我们已经安装了依赖项,让我们为项目创建文件结构。 在项目目录中创建以下目录和文件:
让我们设置项目所需的环境变量。 随着我们的构建逐渐完成,为什么需要每个环境变量会变得逐渐清晰。
避免暴露 API ,因为这存在安全风险。 如果你使用版本控制,你可以设置一个配置文件,可以在不暴露任何秘密的情况下推送,或者你可以创建一个 .env.example 只显示变量名,没有值。
在 /src/config 中创建 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,
};
在根目录中,创建一个 .env 文件。 我们将在构建过程中更新这些值:
PORT=5000
MONGODB_CONNECTION_URL=
JWT_SECRET=
CLOUDINARY_URL=
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: "请求过多,请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("发生错误");
});
module.exports = app;
我们设置了以下中间件:
cors 允许跨源资源共享,这意味着我们的 API 可以从不同的域访问。
代码中添加了限速中间件,可以限制用户在一定时间内允许发出的请求数量。 这有助于防止恶意用户试图用暴力请求攻击服务器:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: true,
message: ""请求过多,请15分钟后重试",
});
app.use(limiter);
我们还有helmet中间件,它通过设置各种 HTTP 标头来帮助保护 API。
然后,是以下中间件,它允许 API 处理请求正文中的 JSON 和 URL 编码数据:
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
最后,我们有 fileUpload 中间件,它允许 API 处理文件上传:
app.use(fileUpload({
createParentPath: true,
useTempFiles: true,
tempFileDir: "/tmp/",
}));
const app = require("./app");
const CONFIG = require("./src/config");
app.listen(CONFIG.PORT, () {
console.log(`server listening on port ${CONFIG.PORT}`);
});
前面我们设置了开发环境和基本文件结构。 现在,让我们设计 API 本身。 在节中,我们将讨论以下主题:
设计API 的第一步是决定要包含的路由和功能。
概述一下我们博客的要求:
考虑到上述要求,我们将定义路由如下:
博客路由:
作者路由: 我们希望只有经过身份验证的用户才能访问这些路由和所有 CRUD 操作。
认证路由: 用于管理用户身份验证。
定义好路由后,我们就可以开始考虑数据库的数据模型了。 数据模型表示将存储在数据库中的数据以及这些数据之间的关系。 我们将使用 Mongoose 来定义我们的架构。
我们将有两个数据模型:Blog和User。
字段名 |
数据类型 |
约束 |
firstname |
String |
required |
lastname |
String |
required |
|
String |
required, unique, index |
password |
String |
required |
articles |
Array, [ObjectId] |
ref - Blog |
字段名 |
数据类型 |
约束 |
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 有一个名为 populate() 的方法,它允许您引用其他集合中的文档。 populate() 将自动用其他集合中的文档替换文档中的指定路径。 User 模型将其 articles 字段设置为 ObjectId 的数组。 ref 选项告诉 Mongoose 在填充期间使用哪个模型,在本例中为Blog模型。 我们在这里存储的所有 _id 必须是博客模型中的文章 _id。 同样,Blog 模型在其author字段中引用了 User 模型。
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 插件应用于blog模型
BlogSchema.plugin(uniqueValidator);
const Blog = mongoose.model("Blog", BlogSchema);
module.exports = Blog;
title字段被定义为必填的字符串,并且在集合中的所有文档中必须是唯一的。 description 字段定义为字符串,tags 字段定义为字符串数组。 author字段定义为对用户集合中文档的引用,timestamp字段定义为日期。 imageUrl 字段定义为字符串,state 字段定义为具有一组允许值(“draft”或“published”)的字符串,readCount 字段定义为默认值为 0 的数字。 readingTime 字段定义为字符串,body 字段定义为必填字符串。
mongoose-unique-validator 是一个插件,它为 Mongoose 模式中的唯一字段添加预保存验证。 如果唯一字段的值已存在于集合中,它将验证模型中的唯一选项,并阻止插入文档。
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 插件应用于用户模型
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 字段定义为对 Blog 集合中文档的引用数组。
pre函数在特定方法执行前执行特定代码。 在将用户文档保存到数据库之前,此处的预保存函数使用 npm 模块 bcrypt 对用户密码进行哈希加密处理。
现在我们已经定义了路由和数据模型,是时候设置数据库连接了。
npm install --save mongoose
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 函数有一个可选的 url 参数,它指定要连接的数据库的 URL。 如果未提供 URL,则默认为“mongodb://localhost:27017”,这将连接到本机运行的MongoDB数据库实例的默认端口 (27017) 。
const database = require("./db");
module.exports = {
database,
};
现在我们已经建立了数据库连接,在下文中,我们将深入探讨两个重要的概念——身份验证和数据验证。
让我们从安装所需的依赖项开始:
npm install joi jsonwebtoken passport passport-jwt passport-local passport-local-mongoose
为了在我们的 API 中对用户进行身份验证,我们将使用流行的 Passport.js 库。 当用户登录时,我们将为用户生成一个 JWT 令牌。 该令牌将用于授权用户对 API 的请求。
在/authentication/passport.js 文件中,设置 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) 策略允许我们通过验证用户的 JWT 来对用户进行身份验证。 要使用此策略,我们传入一个密钥(我们已将其存储在 .env 文件中)和一个函数。
ExtractJWT.fromAuthHeaderAsBearerToken() 方法将从请求的授权标头中提取 JWT 作为 Bearer 令牌。 如果找到 JWT,它将被解码并与 done 函数一起传递给该函数。 该函数将检查 JWT 中的用户对象以查看其是否有效。 如果有效,则调用带有用户对象的 done 函数,表明用户已通过身份验证,否则将调用 done 函数并出错。
对于 /signup 路由,我们将设置一个本地策略,它使用 passport-local 模块通过用户名(在本例中为他们的电子邮件地址)和密码对用户进行身份验证。 当用户注册时,我们将在数据库中创建一个新的用户文档并将其返回给 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.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);
}
}
)
);
我们仍然需要验证用户的密码。 在
/src/models/user.models.js 中,在 User 模型中创建一个名为 isValidPassword() 的方法。
UserModel.methods.isValidPassword = async function (password) {
const user = this;
const match = await bcrypt.compare(password, user.password);
return match;
};
isValidPassword 将密码作为参数,并使用 bcrypt 的比较方法将其与用户的散列密码进行比较。 该方法返回一个布尔值,指示密码是否匹配。
在
src/authentication/passport.js中,在登录策略中调用isValidPassword验证用户密码:
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {message: "Wrong Password" });
}
完整的文件应如下所示:
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 库。 Joi 提供了一种简单但功能强大的方法来定义和验证 Node.js 应用程序中的数据结构。
在 /validators 目录中,创建一个名为 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,
};
我们导出两个中间件函数,newArticleValidationMW 和 updateArticleValidationMW。 newArticleValidationMW 使用
newArticleValidationSchema 来验证新建文章请求的请求正文是否包含所有必填字段(标题、正文)以及所有提供的字段是否格式正确。 如果所有字段都有效,它将调用下一个函数以继续。 updateArticleValidationMW,与前者类似,但它使用 updateArticleValidationSchema 来验证更新文章请求的请求。
这两个函数都使用 Joi 库提供的 validateAsync 方法来执行验证。 此方法接受一个对象(请求主体)并返回一个承诺(promise),如果对象无效则拒绝该对象,如果对象有效则返回该对象。
在 /validators 目录中,创建一个名为 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;
const userValidator = require("./user.validator");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("./author.validator");
module.exports = {
userValidator,
newArticleValidationMW,
updateArticleValidationMW,
};
完成身份验证和验证后,我们就可以开始构建 API 路由和控制器了。
现在我们已经完成了 API 设计、模型设置、数据库连接建立和身份验证策略到位,是时候开始构建实际的 API 了。
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() 函数创建一个新的路由对象,可用于定义将用于处理对服务器的 HTTP 请求的路由。
定义的第一个路由是路由器根路径中的 GET 路由。 此路由将处理对服务器的 HTTP GET 请求,并将使用 blogController 中的 getPublishedArticles 函数返回所有已发布文章的列表。
第二个路由是 GET 路由,在路径中包含一个参数 :articleId。 此路由将处理对路径中具有特定文章 ID 的服务器的 HTTP GET 请求,并将使用 blogController 中的 getArticle 函数返回具有指定 ID 的文章。
在 /routes 目录中创建一个名为 author.routes.js 的文件并编写以下代码:
const express = require("express");
const { authorController } = require("../controllers");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("../validators");
const authorRouter = express.Router();
// 创建新文章
authorRouter.post("/", newArticleValidationMW, authorController.createArticle);
// 改变状态
authorRouter.patch(
"/edit/state/:articleId",
updateArticleValidationMW,
authorController.editState
);
// 编辑文章
authorRouter.patch(
"/edit/:articleId",
updateArticleValidationMW,
authorController.editArticle
);
// 删除文章
authorRouter.delete("/delete/:articleId", authorController.deleteArticle);
// 根据创建的作者获取其创建的文章
authorRouter.get("/", authorController.getArticlesByAuthor);
module.exports = authorRouter;
在这里,
网站栏目:使用 Node.js 构建博客 API
网页路径:http://www.shufengxianlan.com/qtweb/news39/65539.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联