使用 Node.js 构建博客 API

介绍

在本文中,我们将使用 Node.js 构建一个简单的博客 API。 API代表“应用程序编程接口”,它允许不同的软件系统相互通信。 在这种情况下,我们的博客 API 将允许我们创建、读取、更新和删除博客文章,以及管理用户身份验证。

创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站建设、成都网站设计、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的南昌县网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!

为什么要使用 Node.js 构建博客 API? Node.js 是一种流行的开源运行时环境,用于在浏览器外部执行 JavaScript 代码。 它拥有庞大而活跃的开发人员社区以及丰富的库和框架,可以轻松构建可扩展的高性能 Web 应用程序。

先决条件

以下是我们需要的:

  • Node.js: 您可以从官方网站下载最新版本的Node.js,并按照安装说明操作。 此操作还将安装 Node.js 包管理器 (npm),我们将使用它来安装其他包。
  • MongoDB: 我们将使用 MongoDB 数据库来存储数据。 您可以按照官方网站上的安装说明进行操作。
  • 代码编辑器:您可以使用您喜欢的任何代码编辑器,例如 Visual Studio Code、Sublime Text 或 Atom。
  • 终端:我们将使用集成在 VS Code中的终端来运行命令并与项目交互,但您可以使用您喜欢的终端。
  • 基本了解 JavaScript、API、数据库和其他 Web 开发概念。 如果您是这些主题的新手,那么在深入学习本教程之前复习一下您的知识可能会有所帮助。

设置开发环境

创建一个新项目

  • 要创建一个新项目,请打开您的终端并导航到您要创建项目的目录。 然后,运行以下命令:
npm init -y
  • 这将在您的项目目录中创建一个 package.json 文件,用于管理项目的依赖项和脚本。 -y 表示接受所有默认选项,因此您不必手动输入选项。
  • 接下来,运行以下命令来安装这些依赖项:
npm install --save express dotenv cors express-rate-limit helmet express-fileupload

文件结构

现在我们已经安装了依赖项,让我们为项目创建文件结构。 在项目目录中创建以下目录和文件:

  • /src: 这是我们项目的主目录。 它将包含我们应用程序的所有源代码。
  • /src/authentication: 该目录将包含与用户身份验证相关的代码,例如密码哈希加密和 JWT 生成。
  • /src/config: 该目录将包含我们应用程序的配置文件,例如数据库连接字符串和服务器端口号。
  • /src/controllers: 该目录将包含 API 控制器的代码。 控制器处理传入的请求并向客户端发送响应。
  • /src/database: 此目录将包含用于连接到数据库并与之交互的代码。
  • /src/loggers: 该目录将包含用于在我们的应用程序中记录日志的代码。
  • /src/models:该目录将包含我们应用程序的数据库模型的代码。 模型代表存储在数据库中的数据,并提供与该数据交互的接口。
  • /src/routes:该目录将包含 API 路由的代码。 路由定义我们 API 的端点(endpoint)并指定可用于访问它们的 HTTP 方法(例如 GET、POST)。
  • /src/services: 该目录将包含在整个应用程序中使用的服务对象或实用程序函数。
  • /src/validators: 该目录将包含用于验证输入数据的代码。
  • app.js: 该文件将包含服务器的中间件和路由器。
  • index.js: 这是我们应用程序的入口点。 它将包含用于启动服务器和连接到数据库的代码。
  • package.json: 此文件包含 API 的元数据。 启动脚本应该是:“start”:“node index.js”。

设置环境变量

让我们设置项目所需的环境变量。 随着我们的构建逐渐完成,为什么需要每个环境变量会变得逐渐清晰。

避免暴露 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=

设置服务器

  1. 在 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: "请求过多,请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/",
}));
  1. 在index.js 文件中, 添加以下代码:
const app = require("./app");

const CONFIG = require("./src/config");

app.listen(CONFIG.PORT, () {
console.log(`server listening on port ${CONFIG.PORT}`);
});

前面我们设置了开发环境和基本文件结构。 现在,让我们设计 API 本身。 在节中,我们将讨论以下主题:

  • 决定 API 的路由和功能
  • 为数据库定义数据模型
  • 实现数据模型
  • 设置数据库连接

决定路由和功能

设计API 的第一步是决定要包含的路由和功能。

概述一下我们博客的要求:

  • 用户应该能够注册并登录博客应用程序。
  • 博客文章可以处于两种状态:草稿和已发布。
  • 用户能够获得已发表文章的列表,无论他是否登录。
  • 用户应该能够获得已发表的文章,无论他是否登录。
  • 登录用户应该能够创建文章。
  • 创建文章时,它应该处于草稿状态。
  • 文章的作者应该能够将文章的状态更新为已发布。
  • 文章的作者应该能够编辑处于草稿或已发布状态的文章。
  • 文章的作者应该能够删除处于草稿或已发布状态的文章。
  • 文章的作者应该能够得到他们文章的列表。结果内容应该按状态分页和过滤。
  • 创建的文章应该有标题、封面图片、描述、标签、作者、时间戳、状态、阅读次数、阅读时间和正文。
  • 登录和未登录用户都可以访问的文章列表端点,内容应该分页。
  • 博客文章应该可以按作者、标题和标签进行搜索。
  • 博客文章还应该可以按阅读次数、阅读时间和时间戳排序
  • 请求单篇文章时,API应随博客文章同时返回作者信息,博客阅读次数加1。

考虑到上述要求,我们将定义路由如下:

博客路由:

  • GET /blog: 检索所有已发表文章的列表。
  • GET /blog/:article_id:通过 ID 检索单篇文章。

作者路由: 我们希望只有经过身份验证的用户才能访问这些路由和所有 CRUD 操作。

  • GET /author/blog: 检索用户创建的所有已发布文章的列表。
  • POST /author/blog: 创建新文章。
  • PATCH /author/blog/edit/:article_id: 通过 ID 更新文章。
  • PATCH /author/blog/edit/state/:article_id: 更新文章的状态。
  • DELETE /author/blog/:article_id: 按 ID 删除文章。

认证路由: 用于管理用户身份验证。

  • POST /auth/signup: 注册一个新用户。
  • POST /auth/login: 登录现有用户。

定义数据模型

定义好路由后,我们就可以开始考虑数据库的数据模型了。 数据模型表示将存储在数据库中的数据以及这些数据之间的关系。 我们将使用 Mongoose 来定义我们的架构。

我们将有两个数据模型:Blog和User。

User

字段名

数据类型

约束

firstname

String

required

lastname

String

required

email

String

required, unique, index

password

String

required

articles

Array, [ObjectId]

ref - Blog

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 模型。

实现数据模型

  • 在 /src/models目录下, 创建一个名为 blog.model.js 的文件并设置 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 插件应用于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 模式中的唯一字段添加预保存验证。 如果唯一字段的值已存在于集合中,它将验证模型中的唯一选项,并阻止插入文档。

  • 在 /src/models目录下, 创建一个名为 user.model.js 的文件并设置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 插件应用于用户模型
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 对用户密码进行哈希加密处理。

设置数据库连接

现在我们已经定义了路由和数据模型,是时候设置数据库连接了。

  • 设置您的 MongoDB 数据库并将连接 url 保存在您的 .env 文件中。
  • 运行以下命令安装 npm 包 mongoose:
npm install --save mongoose
  • 在 /database 目录中创建一个名为 db.js 的文件。 在 /database/db.js 中,使用 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) 。

  • 在 /database 目录下创建一个 index.js 文件:
const database = require("./db");

module.exports = {
database,
};

现在我们已经建立了数据库连接,在下文中,我们将深入探讨两个重要的概念——身份验证和数据验证。

让我们从安装所需的依赖项开始:

npm install joi jsonwebtoken passport passport-jwt passport-local passport-local-mongoose

使用 Passport.js 进行身份验证

为了在我们的 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-local 模块。 当用户登录时,我们会在数据库中搜索匹配的电子邮件地址,并验证该用户是否存在于数据库中。 如果登录成功,我们将用户对象返回给 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);
}
}
)
);

我们仍然需要验证用户的密码。 在
/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 库。 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;
  • 在/validators/index.js文件中:
const userValidator = require("./user.validator");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("./author.validator");

module.exports = {
userValidator,
newArticleValidationMW,
updateArticleValidationMW,
};

完成身份验证和验证后,我们就可以开始构建 API 路由和控制器了。

现在我们已经完成了 API 设计、模型设置、数据库连接建立和身份验证策略到位,是时候开始构建实际的 API 了。

  • 为 API 实现路由。
  • 编写控制器来处理请求和发送响应。
  • 使用 Postman 测试 API。

实现路由

  1. 在 /routes 目录中创建一个名为 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() 函数创建一个新的路由对象,可用于定义将用于处理对服务器的 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。内容未经允许不得转载,或转载时需注明来源: 创新互联