Node.js 构建社交媒体REST API:完整指南
开发人员构建一个 API(应用程序编程接口) ,允许其他系统与其应用程序的功能和数据进行交互。简单来说,API是一组协议、规则和工具,允许不同的软件应用程序访问允许的功能和数据,并彼此交互。API是为用户应用程序创建的服务,用于请求来自服务器的数据或某些功能。
在本文中,我们将构建一个RESTful社交媒体API,其中我们将创建一个通用的社交媒体应用的后端结构,用户可以注册,如果用户是真实的,则可以创建帖子。此外,他们还可以删除自己的特定帖子。但在直接进行之前,我们将学习为什么创建API并选择API的技术栈。
为什么要创建API
如果某些应用程序需要从我们的系统中提取少量数据,我们不能为了各种安全问题而完全授予他们对我们服务器的访问权限。相反,我们为特定数据或功能创建结构良好且安全的API,以便如果有人需要,我们可以给他们适当的API密钥和终端点,只有真实用户才能访问这些数据,这可以通过正确的身份验证(如OAuth 2.0和JSON Web Tokens)进行检查。
我们还可以监测和分析不同用户对API的使用情况,以便在发生问题时安全处理,并随着时间的推移改进我们的API以实现更好的利用。
例如:许多大公司,如Swiggy,Zomato和OLA,使用地图API将地图集成到应用程序中,以提供其骑手的实时位置并为客户提供更好的体验。
在开始构建API之前,让我们讨论将整个API构建在其上的技术栈。
选择技术栈
在构建API时选择正确的技术栈非常重要。技术栈包括用于开发API的编程语言、框架、库和数据库。尽管有许多因素可用于选择构建API的技术栈,例如可伸缩性、性能、安全性和开发人员生产力,但总是要选择提供可伸缩性选项的技术,例如分布式系统、负载平衡和缓存机制。还要选择与项目要求和开发团队的专业知识相匹配的编程语言和框架。
因此,在本文中,我们将了解如何构建一个基于Node.js的RESTful社交媒体Web API,其技术栈包括:
- Node.js – 用于创建路由和服务器。
- MongoDB – 在其上执行CRUD(创建、读取、更新、删除)操作的数据库。
- REST – 可扩展并在HTTP协议上工作的表现层状态转移。
- Postman – 此外,我们将使用Postman进行API测试。现在让我们更清楚地了解它。
如何使用Node.js构建社交媒体REST API
我们将构建一个名为social_media的REST API。我们将创建用户,添加身份验证功能,并允许经过身份验证的用户创建帖子。所有数据将存储在MongoDB数据库中。我们还将调用API通过ID删除特定帖子并获取用户详细信息,而所有请求都将发送到Postman中。
由于本文是关于构建API的内容,所以我们在这里 不创建任何前端 ,但是为了测试和请求数据,我们将使用Postman。
以下是构建社交媒体API的步骤:
步骤1:初始化Node.js项目
首先,在终端上运行以下命令,创建一个名为social_media的新文件夹,并在该文件夹内初始化NPM。
npm init -y
它将初始化您的项目并创建一个包含项目详细信息的package.json文件。
输出:
步骤2:安装必要的模块
使用以下命令安装此项目需要的模块。
npm i <module_name, module_name...>
在下面给出的模块中将module_name替换为模块的名称。
- bcrypt: 用于通过散列来保护密码安全
- dotenv: 用于存储我们的秘钥和MongoDB的URL
- express: 一个node.js框架
- express-validator: 用于验证用户的输入数据,如邮箱和电话号码
- jsonwebtoken: 用于认证和授权用户
- mongoose: 将MongoDB连接到我们的应用程序
如果需要其他模块,我们可以按照相同的方式进行安装。
输出:
步骤3: 创建服务器
是时候创建我们的服务器了。在当前目录下创建一个 server.js 文件。导入 express 库,使用它创建一个应用,并在端口号为 3000 上监听服务器。将下面的代码添加到 server.js 文件中。
server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
module.exports = app;
现在我们将通过在终端中输入以下命令来运行文件。
输出:
恭喜!您的服务器已启动。
步骤4:创建数据库
因此我们将使用MongoDB Atlas来创建我们的social_media数据库。我们正在使用它的网站。
登录MongoDB后,点击下图所示的Create按钮
4.1. 点击创建集群
4.2. 创建一个名为“social_media”的新项目。
4.3. 保存下面显示的用户名和密码,将用于与数据库建立连接。
4.4 . 前往网络访问,然后选择编辑,允许从任何地方访问,然后确认。
4.5 . 现在您需要通过添加一个链接将此数据库添加到应用程序中。找到链接的方法是,进入数据库 -> 连接 -> 指南仪,您将看到下面的图像:
步骤5: 连接MongoDB至应用程序
为此,我们将在同一目录下创建一个新文件‘db.js’,并在其中编写连接代码,然后将此文件连接到我们的server.js文件。
现在在同一目录下创建一个db.js文件。
db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (error) {
console.error(error.message);
process.exit(1);
}
};
module.exports = connectDB;
在这里,我们需要使用mongoose库与数据库建立连接。‘process.env.MONGODB_URI’将会给出在.env文件中存储的链接,用于连接我们的服务器与数据库。如果出现任何错误,它将会捕获并在控制台中显示。
在相同目录下创建一个.env文件来存储秘密密钥和连接MongoDB的URL,以确保连接安全。
.env
MONGODB_URI=”mongodb+srv://<replaceyourusername>:<pasteyourpassword>@cluster0.g73tlip.mongodb.net/?retryWrites=true&w=majority”
JWT_SECRET=mysecret
在这里,用<replaceyourusername>
替换你的用户名,并用<pasteyourpassword>
替换你的密码。
现在通过将以下代码添加到server.js文件中,将db.js文件连接到server.js文件中。
server.js
const connectDB = require('./db');
require('dotenv').config();
connectDB();
在本节末尾查看最终更新的server.js文件。
现在我们将运行我们的server.js文件。
输出:
在这里,您可以看到我们的数据库已经成功连接。
步骤6: 为用户和帖子创建模式
到目前为止,我们的数据库已经连接,所以现在是时候为我们的用户和他们将创建的帖子创建模式了。
用户将包含4个字段 – 名字、邮箱、密码和帖子。用户的模式将如下所示:
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
}
在新用户注册时,姓名、电子邮件和密码是必需的。
帖子模式将包含4个字段:作者、标题、描述和创建时间,它的格式如下:
{
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
title: {
type: String,
required: true
},
description:{
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
我们将在不同的文件中创建用户和帖子的模式,并且为了创建这些文件,我们将在主目录下创建一个名为’models’的目录,其中包含有users.js和posts.js文件,这些文件将包含相应的模式和模型。
userSchema.pre()和postSchema.pre()用于在将数据添加到数据库之前执行操作。
在user.js文件中,使用bcrypt对密码进行散列处理,以提高其安全性。我们将使用散列处理转换密码,然后将其存储在数据库中。
为User模式创建一个user.js文件。
models/user.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
});
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password') || user.isNew) {
try {
const hash = await bcrypt.hash(user.password, 10);
user.password = hash;
} catch (error) {
return next(error);
}
}
next();
});
const User = mongoose.model('User', userSchema);
module.exports = User;
在添加帖子之前,将使用postSchema.pre()来更新userSchema中的新帖子。
创建一个post.js文件用于帖子模式。
models/posts.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
title: {
type: String,
required: true
},
description:{
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
postSchema.pre('save', async function() {
try {
// Find the user document and update its posts array with the new post
const user = await mongoose.model('User').findByIdAndUpdate(
this.author,
{ $push: { posts: this._id } },
{ new: true }
);
} catch (err) {
console.error(err);
}
});
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
现在我们的目录看起来是这样的:
现在我们的目录看起来是这样的:
步骤7:创建/注册新用户
我们将使用POST方法创建两个新用户并将它们添加到我们的数据库中。
POST方法的语法:
router.post(route, (req, res)=>{
...
})
添加用户时,我们将创建另一个目录 ‘routes’,该目录将包含所有的路由和我们的 users.js 文件。
routes/users.js
// Import necessary modules
const express = require('express');
const router = express.Router();
const User = require('../models/users');
// Create a new user
router.post('/register', async (req, res) => {
const { name, email, password } = req.body;
// Create a new user with the provided name, email, and password
const user = new User({ name, email, password });
await user.save();
// Return the new user as JSON
res.json(user);
});
module.exports = router;
当用户调用/register API时,由于它是POST请求,所以它将携带一些数据。使用req.body获取数据,并将数据赋值给 {name, email, password},然后将此数据添加到模式中。
要将routes/users.js文件连接到服务器,我们将以下代码添加到我们的server.js文件中:
server.js
const userRouter = require('./routes/users');
app.use('/api',userRouter);
查看本节末尾的server.js更新后的代码。
现在,当我们访问这个路由并使用POST方法将数据发送到服务器时,它将把我们的数据存储到数据库中。
现在使用Postman创建两个用户:
登录Postman后创建一个名为social_media的新集合,在那里创建一个新的请求,并选择POST方法,在body中选择raw并将TEXT更改为JSON格式,在输入框中添加新用户的数据。
现在添加api/register的URL并点击发送按钮。
在输出的主体中,我们将以JSON格式看到存储在我们的数据库中的数据。
同样地,创建另一个用户。
转到 social_media 数据库,然后如果我们刷新数据库上的 Browse 集合,就会以 JSON 格式找到 2 个用户的数据。
步骤8:获取用户的详细信息
现在我们将使用GET方法获取用户的所有详细信息。但在此之前,我们将检查用户是否经过身份验证,为此,我们使用一个名为“auth”的中间件,它将通过使用JSON Web Token来检查用户是否经过身份验证。
所以首先创建一个名为“middleware”的文件夹,其中包含“auth.js”文件。
它将接收一个令牌并检查是否存在该令牌,然后使用该令牌创建一个userID并检查是否存在具有该userID的用户,如果存在则传递该用户,否则抛出一个错误。
middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/users');
module.exports = async function(req, res, next) {
if(!req.headers.authorization){
return res.status(401).json({ message: 'Unauthorized' });
}
const token = req.headers.authorization.split(' ')[1];;
// console.log(req);
// Check if the token exists
if (!token) {
return res.status(401).json({ message: 'Unauthorized' });
}
try {
// Verify the token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// console.log(decoded);
// Add the decoded user information to the request object
const user = await User.findById(decoded.userId);
if(!user){
return res.status(400).json({ message: 'User does not exist' });
}
req.user = user;
// Call the next middleware function
next();
} catch (err) {
res.status(401).json({ message: 'Invalid token' });
}
};
我们从注册用户那里获取一个令牌,并检查用户是否匹配,如果用户匹配,则允许请求进一步进行,否则抛出错误。
这里我们手动生成令牌。为了生成令牌,我们在‘routes’目录中创建一个‘auth.js’文件,该文件将检查用户的详细信息并生成令牌(如果用户存在)。
当我们访问/authenticate并提供电子邮件和密码时,它将检查所需密码是否与电子邮件存在,如果用户存在,则返回一个JSON Web令牌作为响应。
routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/users');
const bcrypt = require("bcrypt");
const router = express.Router();
router.post('/authenticate', async (req, res) => {
const { email, password } = req.body;
if(!email){
return res.status(400).json({ message: '"email" is required' });
}
if(!password){
return res.status(400).json({ message: '"password" is required' });
}
// Find the user by email
const user = await User.findOne({ email });
// If the user doesn't exist or the password is incorrect, return an error
if(!user){
return res.status(401).json({ message: 'Email or password is incorrect' });
}
const validPassword = await bcrypt.compare(password, user.password)
if (!validPassword) {
return res.status(401).json({ message: 'Email or password is incorrect' });
}
// Generate a JWT token with the user ID as payload
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET);
// Return the token as JSON
res.json({ token });
});
module.exports = router;
将上述文件连接到server.js文件中,方法是在“server.js”文件中添加以下代码:
server.js
const authRouter = require('./routes/auth');
app.use('/api', authRouter);
在本节末尾查看server.js文件的最终更新代码。
现在,要获取用户的详细信息,我们必须创建一个名为/user的路由,该路由将使用中间件检查用户是否认证,然后返回用户的详细信息。
在’routes/users.js’中添加以下代码以获取用户的详细信息
routes/users.js
Cout auth= require(‘../middleware/auth’);
router.get('/user', auth, async (req, res) => {
try {
const userId = req.user.id;
const user = await User.findById(userId);
const userName = user.name;
res.json({ name: userName });
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
现在让我们来看看Postman:
首先,在auth.js文件中使用/authenticate路由为您想要获取详细信息的用户创建一个令牌:
创建一个新的请求,选择POST方法,在body中选择raw并选择JSON格式,在输入字段中添加{email,password}作为JSON。添加api/authenticate的URL并点击发送按钮,它将生成Token作为输出。
现在将这个令牌发送给中间件进行身份验证,并在Postman中访问用户路由以获取用户的详细信息,因此我们将在Postman的身份验证中将令牌作为 bearer 令牌传递,如下图所示:
因此作为响应,它将以用户的详细信息作为输出显示给我们。
步骤9:为用户创建帖子
同样,我们可以使用POST方法为特定用户创建帖子。现在我们将在routes中创建另一个名为’posts.js’的文件,其中包含创建帖子的路由。
在routes中创建一个名为’posts.js’的文件,其中包含路由。在这里,我们使用了一个重要的库’express-validator’,它向路由添加了一个中间件’check’,而validateResult将检查请求是否出现任何错误,然后通过从请求中获取数据创建一个新的帖子。
routes/posts.js
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const { check, validationResult } = require('express-validator');
const Post = require('../models/posts');
router.post('/posts',
[auth, [check('title', 'Title is required').not().isEmpty(), check('description', 'Description is required').not().isEmpty()]],
async (req, res) => {
// Check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ message: 'Invalid inputs'});
}
try {
// Create a new post
const post = new Post({
title: req.body.title,
description: req.body.description,
author: req.user.id
});
// Save the post to the database
await post.save();
// Return the new post object
res.json({
id: post.id,
title: post.title,
description: post.description,
createdAt: post.createdAt
});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
module.exports = router;
将此文件与 server.js 文件连接起来,通过将以下代码添加到 server.js 文件中:
server.js
const postRouter = require('./routes/posts');
app.use('/api',postRouter);
查看此部分末尾的最终更新的server.js文件
现在首先在Postman上为您要创建帖子的用户创建一个令牌
将这个令牌拿起来,创建一个新的请求,并将它添加到身份验证部分的承载令牌中,然后以JSON格式的帖子数据进行POST请求。
然后刷新数据库中的浏览集合,您将发现帖子已经成功地创建并存储在数据库中,如下图所示:
步骤10:使用DELETE方法通过帖子ID删除用户的帖子
在’routes/posts.js’文件中创建路由。
首先,我们将使用findOne函数检查postID是否存在。如果存在,则使用deleteOne函数将其删除。
将以下代码添加到’routes/posts.js’文件的’/posts’路由之后:
routes/posts.js
// DELETE a post by ID
router.delete('/posts/:id', auth, async (req, res) => {
try {
// Find the post by ID and verify it was created by the authenticated user
const post = await Post.findOne({ _id: req.params.id, author: req.user.id });
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
// Delete the post and its associated comments
await Post.deleteOne({ _id: req.params.id });
resstatus(204).json({ message: 'Post deleted' });
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
首先为您要删除帖子的用户创建一个令牌
现在获取要删除的帖子的ID:
现在创建一个新的DELETE请求,并将token添加到bearer token进行身份验证,将id添加到路由中并发送。
现在如果您刷新页面并查看数据库中的Browse collection,您将发现Post不存在:
帖子已被删除。现在,如果您查看目录的最终结构,它将如下所示:
我们已完成社交媒体API项目。最终更新的文件已进行多次使用:
server.js
const express = require('express');
const connectDB = require('./db');
const authRouter = require('./routes/auth');
const userRouter = require('./routes/users');
const postRouter = require('./routes/posts');
const app = express();
const PORT = process.env.PORT || 3000;
require('dotenv').config();
connectDB();
app.use(express.json());
app.use('/api',userRouter);
app.use('/api',authRouter);
app.use('/api',postRouter);
app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
module.exports = app;
构建API的最佳实践和提示
构建API需要开发人员进行详细的规划并坚持最佳实践,以使其更安全、用户友好和结构化。以下是在API开发过程中,软件行业的开发人员遵循的一些最佳实践提示:
- 遵循RESTful原则 :现在软件行业都遵循REST(表征性状态转移)原则。因此,请设计符合REST原则的API。同时,使用清晰和有意义的资源命名,并且响应应该具有适当的状态码。
- 使用版本控制 :如果从一开始就实现版本控制,那么您就可以在不破坏现有API集成的情况下进行必要的更改和添加新功能。
- 添加身份验证和授权 :很容易理解,将数据和功能权限授予匿名用户可能会有害。因此,建议为安全访问API实施强大的身份验证,并根据用户角色授权访问权限。
- 错误处理和状态码 :为了使用户在使用您的API时获得良好的体验,请为请求的成功和失败使用适当的HTTP状态码。实施合适的错误处理,以便为开发人员提供有意义且易于理解的错误消息和错误代码。
- 测试和改进您的API :定期测试您的API以解决任何问题,并实施必要的更改以改进它。您可以进行负载测试和性能测试来测试您的API。
- 分析API使用情况 :通过实施必要的机制来监控您的API的性能、错误和使用情况。收集这些数据,并进行分析,以获取改进API的见解。
- API的可扩展性 :以这样的方式设计您的API,如果未来需求增加,您可以扩展您的API,以处理不断增加的流量,并保持良好的性能。
- 创建文档 :为您的API提供完整和清晰的文档。解释端点的请求和响应格式,所需的身份验证以及使用您的API所需的任何其他重要信息指南。
因此,这些是您可以遵循的一些提示,以构建可扩展、安全和开发人员友好的API。
结论
在本文中,我们学习了API对于开发人员构建大型可扩展系统的重要性。我们从头开始构建了一个social_media API,并对MongoDB数据库进行了一些CRUD操作。最后,我们了解了一些构建良好API的最佳实践。