使用 ReactJS、NodeJS、ExpressJs 和 Redis-OM 构建全栈应用程序
概述 根据_Meik Wiking_(《记忆的艺术》的作者)的说法,快乐的记忆对我们的心理健康至关重要。它们增强了我们的认同感和目的感,并加强了我们的关系。快乐的回忆是当下幸福的重要组成部分。因此,这催生了我的项目,Memories App,它允许每个人在任何时间点记录他们的记忆。 事不宜迟,让我们导航到项目详细信息。 序言 Redis 是一个 NoSQL 数据库,因其简单性和速度而备受喜爱。在这
概述
根据_Meik Wiking_(《记忆的艺术》的作者)的说法,快乐的记忆对我们的心理健康至关重要。它们增强了我们的认同感和目的感,并加强了我们的关系。快乐的回忆是当下幸福的重要组成部分。因此,这催生了我的项目,Memories App,它允许每个人在任何时间点记录他们的记忆。
事不宜迟,让我们导航到项目详细信息。
序言 Redis 是一个 NoSQL 数据库,因其简单性和速度而备受喜爱。在这篇博文中,我们将使用 Redis 数据库构建一个全栈应用程序来存储数据。我们将使用允许创建、检索、更新和执行全文搜索的 Redis-OM 功能构建我们的模型。如果您不熟悉 Redis 数据库,那完全可以。
首先:
什么是 Redis? 嗯,Redis 是一种内存键值存储,通常用作缓存,以使传统数据库更快。然而,它已经发展成为一个多模型数据库,能够进行全文搜索、图形关系、人工智能工作负载等。
什么是 RedisJSON? RedisJSON 是一个 Redis 模块,它在 Redis 中提供 JSON 支持。 RedisJSON 允许您在 Redis 中存储、更新和检索 JSON 值,就像使用任何其他 Redis 数据类型一样。 RedisJSON 还可以与 RediSearch 无缝协作,让您可以索引和查询 JSON 文档。这是使用 Redis 数据库的最佳方式。
什么是 Redis-OM Redis OM(发音为 REDiss OHM)是一个为 Redis 提供对象映射的库——这就是 OM 的意思......对象映射。它将 Redis 数据类型(特别是哈希和 JSON 文档)映射到 JavaScript 对象。它允许您搜索这些哈希和 JSON 文档。它使用 RedisJSON 和 RediSearch 来做到这一点。
RedisJSON 添加了 JSON 文档数据类型和操作它的命令,RediSearch 添加了各种搜索命令来索引 JSON 文档和哈希的内容。
让我们使用 Redis 构建并利用这些 Redis-OM 功能!
设置
启动 Redis 首先,我们将设置我们的环境。然后,出于开发目的,我们将使用 Docker 来运行 Redis Stack:
docker run -p 10001:6379 -p 13333:8001 redis/redis-stack:latest
这将启动您的 Redis 服务器,一旦启动,继续构建后端服务器
初始化您的应用程序
- 在您的项目目录中,导航到终端并运行以下命令:
mkdir server
cd server
npm run init -y
-
这会在你的服务器文件夹中创建一个 package.json 和 package-lock.json 文件,现在你可以开始安装你的依赖了。我们将安装以下软件包:redis-om、cors、express、nodemon 以及其他依赖项
-
运行以下命令安装依赖
npm i cors express nodemon redis-om bcrypt body-parser colors dotenv express express-validator jsonwebtoken nodemon redis redis-om
- 在进行应用程序的最后一部分之前,让我们创建我们的 env 文件,在服务器文件夹中创建 .env 并添加
PORT=3005
REDIS_HOST="redis://localhost:6379"
JWT_TOKEN_SECRET= "add your preferred jwt secret"
JWT_EXPIRE="add the expiry"
- 将 dotenv 作为依赖项的一部分安装后,我们可以开始在应用程序中使用环境变量数据。
创建并连接 Redis 客户端
- 要创建和连接 Redis 客户端,在 server 文件夹中创建 config 文件夹并创建一个名为 connectToRedis.js 的文件
config/connectToRedis.js
import { Client } from "redis-om";
import dotenv from "dotenv";
const url = process.env.REDIS_HOST;
let client;
try {
client = await new Client().open(url);
console.log("##########################################################");
console.log("##### REDIS STORE CONNECTED #####");
console.log("##########################################################\n");
} catch (err) {
console.log(`Redis error: ${err}`.red.bold);
}
export default client;
创建模型/存储库
- 在server文件夹中创建一个名为model的文件夹,分别添加post.js和user.js这两个文件。
model/post.js
import { Entity, Schema } from "redis-om";
import client from "../config/connectToRedis.js";
class Post extends Entity {}
const postSchema = new Schema(
Post,
{
title: { type: "text" },
message: { type: "text" },
name: { type: "string" },
creator: { type: "string" },
tags: { type: "text" },
selectedFile: { type: "string" },
likes: { type: "string[]" },
comments: { type: "string[]" },
createdAt: { type: "date", sortable: true },
},
{
dataStructure: "JSON",
}
);
export const postRepository = client.fetchRepository(postSchema);
await postRepository.createIndex();
model/user.js
import { Entity, Schema } from "redis-om";
import client from "../config/connectToRedis.js";
class User extends Entity {}
const userSchema = new Schema(
User,
{
name: {
type: "string",
},
email: {
type: "string",
},
password: {
type: "string",
},
},
{
dataStructure: "JSON",
}
);
export const userRepository = client.fetchRepository(userSchema);
await userRepository.createIndex();
-
对于这两种模式,我们定义了一个实体。实体是在您使用它时保存您的数据的类——被映射到的东西。它是您创建、阅读、更新和删除的内容。任何扩展 Entity 的类都是实体。
-
我们还为两者定义了模式;架构定义实体上的字段、它们的类型以及它们如何在内部映射到 Redis。默认情况下,实体映射到 JSON 文档。
注意:在字段类型中,文本字段很像字符串。不同之处在于,字符串字段只能在其整个值上匹配——不能部分匹配——并且最适合键,而文本字段启用了全文搜索并针对人类可读的文本进行了优化。
- 我们还为每个创建了一个存储库。 Repository 是 Redis OM 的主要接口。它为我们提供了读取、写入和删除特定实体的方法
最后...
- 我们创建了一个索引,以便我们能够在每个存储库中搜索数据。我们通过调用 .createIndex() 来做到这一点。如果一个索引已经存在并且它是相同的,这个函数不会做任何事情。如果它不同,它将删除它并创建一个新的
设置路线
- 创建一个路由文件夹并添加包含我们应用程序路由的posts.js 和users.js 文件。它应该像我们下面的内容:
routes/posts.js
import express from "express";
const router = express.Router();
import { getPosts, getPost, getPostsBySearch, createPost, updatePost, deletePost, likePost, commentPost, getMyPosts, getSuggestedPosts, } from "../controller/posts.js";
import validator from "../middleware/validator.js";
import auth from "../middleware/auth.js";
import schema from "../validation/post.js";
const { postSchema } = schema;
router.route("/").get(auth, getMyPosts).post(auth, validator(postSchema), createPost);
router.route("/all").get(getPosts);
router.get("/search", getPostsBySearch);
router.get("/suggestion", getSuggestedPosts);
router.route("/:id").patch(auth, updatePost).delete(auth, deletePost);
router.get("/:id", getPost);
router.patch("/:id/comment", commentPost);
router.patch("/:id/like", auth, likePost);
export default router;
routes/users.js
import express from "express";
const router = express.Router();
import { signin, signup } from "../controller/user.js";
import validator from "../middleware/validator.js";
import schema from "../validation/user.js";
const { userSchema } = schema;
router.route("/signin").post(signin);
router.route("/signup").post(validator(userSchema), signup);
export default router;
请注意,验证器是使用 express 验证器完成的,因此我们导入了帖子和用户验证模式,并确保在将请求发送到端点时首先验证我们的数据。您可以在此处访问必要的验证文件验证文件和验证器中间件
设置 Index.js 文件
在服务器文件夹中创建 Index.js 文件
index.js
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
import dotenv from "dotenv";
import colors from "colors";
import client from "./config/connectToRedis.js";
import postRoutes from "./routes/posts.js";
import { errorHandler, notFound } from "./middleware/error.js";
import userRoutes from "./routes/users.js";
const app = express();
dotenv.config();
//Body Parser
app.use(bodyParser.json({ limit: "30mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(cors());
app.get("/", (req, res) => {
res.json({ message: "Hello to Memories API" });
});
app.use("/posts", postRoutes);
app.use("/user", userRoutes);
const PORT = process.env.PORT || 5000;
const server = app.listen(PORT, () => {
console.log("##########################################################");
console.log("##### STARTING SERVER #####");
console.log("##########################################################\n");
console.log(`server running on → PORT ${server.address().port}`.yellow.bold);
});
process.on("uncaughtException", (error) => {
console.log(`uncaught exception: ${error.message}`.red.bold);
process.exit(1);
});
process.on("unhandledRejection", (err, promise) => {
console.log(`Error ${err.message}`.red.bold);
server.close(() => process.exit(1));
});
app.use(notFound);
app.use(errorHandler);
在这里,我们使用了不同的中间件,并在文件中导入了我们连接的 Redis 客户端以及创建的路由。
哎呀,同志,我们到了那里!,进入我们应用程序的最后一部分!,
设置控制器
这是您的应用程序逻辑所在的位置。对于用户注册,我们将使用 JWT 对凭据进行签名,并使用 bcrypt 对密码进行加密,然后再将其存储到我们的数据库中。
-
从 /register 路由,我们将:
-
获取用户输入。
-
验证用户是否已经存在。
-
加密用户密码。
-
在我们的数据库中创建一个用户。
-
最后,创建一个签名的 JWT 令牌。
controller/user.js
export const signin = async (req, res) => {
const { email } = req.body;
const existingUser = await userRepository.search().where("email").is.equalTo(email).return.first();
//check if user exists
if (!existingUser) {
return res.status(404).json({ message: "User not found." });
}
//check for correct password
const isPasswordCorrect = await bcrypt.compare(req.body.password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(404).json({ message: "invalid Credentials" });
}
//create auth token
const token = jwt.sign({ email: existingUser.email, id: existingUser.entityId }, process.env.JWT_TOKEN_SECRET, {
expiresIn: process.env.JWT_EXPIRE,
});
const { entityId, password, ...rest } = existingUser.toJSON();
const data = { id: existingUser.entityId, ...rest };
res.status(200).json({ result: data, token });
};
- 使用 Postman 测试端点,注册成功后会得到如下响应。
在整个应用程序中,请注意 id 如何替换 Redis-OM 为我们提供的 entityId 以匹配传统响应。
const { entityId, password, ...rest } = user.toJSON();
const data = { id: user.entityId, ...rest };
-
用于登录路由
-
获取用户输入。
-
验证用户。
-
最后,创建并发送一个签名的 JWT 令牌。
controller/user.js
export const signin = async (req, res) => {
const { email } = req.body;
const existingUser = await userRepository.search().where("email").is.equalTo(email).return.first();
//check if user exists
if (!existingUser) {
return res.status(404).json({ message: "User not found." });
}
//check for correct password
const isPasswordCorrect = await bcrypt.compare(req.body.password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(404).json({ message: "invalid Credentials" });
}
//create auth token
const token = jwt.sign({ email: existingUser.email, id: existingUser.entityId }, process.env.JWT_TOKEN_SECRET, {
expiresIn: process.env.JWT_EXPIRE,
});
const { entityId, password, ...rest } = existingUser.toJSON();
const data = { id: existingUser.entityId, ...rest };
res.status(200).json({ result: data, token });
};
- 使用 Postman 测试端点,登录成功后会得到如下响应。
😊 并且用户可以开始创建他或她的记忆
好吧,让我们深入了解后控制器...
创建帖子
controller/post.js
export const createPost = async (req, res) => {
const post = req.body;
const newPost = await postRepository.createAndSave({
...post,
creator: req.userId,
createdAt: new Date().toISOString(),
});
const { entityId, ...rest } = newPost.toJSON();
res.status(201).json({ data: { id: newPost.entityId, ...rest } });
};
获取分页帖子
controller/post.js
export const getPosts = async (req, res) => {
const { page } = req.query;
const limit = 8;
const offset = (page - 1) * limit;
if (!page) {
res.status(400).json({ message: "Enter count and offset" });
}
const posts = await postRepository.search().sortDescending("createdAt").return.page(offset, limit);
const postsCount = await postRepository.search().sortDescending("createdAt").return.count();
const newPosts = posts.map((item) => {
const { entityId, ...rest } = item.toJSON();
return { id: item.entityId, ...rest };
});
res.status(200).json({ data: newPosts, currentPage: Number(page), numberOfPages: Math.ceil(postsCount / limit) });
};
获取帖子
controller/post.js
export const getPost = async (req, res) => {
const data = await postRepository.fetch(req.params.id);
if (!data.title) {
return res.status(404).json({ message: "No post with that id" });
}
const { entityId, ...rest } = data.toJSON();
res.status(200).json({ data: { id: data.entityId, ...rest } });
};
删除帖子
controller/post.js
export const deletePost = async (req, res) => {
const post = await postRepository.fetch(req.params.id);
if (!post.title) {
return res.status(404).json({ message: "No post with that id" });
}
if (req.userId !== post.creator) {
return res.status(400).json({ message: "Unauthorized, only creator of post can delete" });
}
await postRepository.remove(req.params.id);
res.status(200).json({ message: "post deleted successfully" });
};
就像一个帖子
controller/post.js
export const likePost = async (req, res) => {
let post = await postRepository.fetch(req.params.id);
if (!post.title) {
return res.status(400).json({ message: "No post with that id" });
}
if (!post.likes) {
post.likes = [];
}
const index = post.likes.findIndex((id) => id === String(req.userId));
if (index === -1) {
post.likes = [...post.likes, req.userId];
} else {
post.likes = post?.likes?.filter((id) => id !== String(req.userId));
}
await postRepository.save(post);
const { entityId, ...rest } = post.toJSON();
res.status(200).json({ data: { id: post.entityId, ...rest } });
};
- 我们将在这里停止对我们的 post 端点的请求。
Redis 云平台
-
最后一点,让我们浏览一下 Redis 云平台
-
在Redis上创建一个账户并登录。
-
导航到仪表板以创建新订阅。您可以从免费套餐开始,稍后根据您的应用使用情况进行升级。
-
创建一个名为Memories App的新数据库
注意端点在与该项目一起准备的前端应用程序中使用
投稿类别:
- MEAN/MERN Maverick:Redis 作为主数据库而不是 MongoDB(即将 MEAN/MERN 中的“M”替换为 Redis 的“R”)。
项目短视频说明:
- Youtube
使用的技术
-
JS/Node.js
-
快递js
-
JSON Web 令牌
-
ReactJs, Redux
-
材质界面
代码链接
- Github
部署
要使部署工作,您需要在Redis Cloud上创建免费帐户
Heroku
部署的后端链接
网络化
前端记忆应用
其他资源/信息
-
Redis-OM Node Github Repo
-
Redis OM for Node.js
-
观看此视频,了解 Redis Cloud 相对于其他 Redis 提供商的优势
-
Redis Developer Hub - 关于 Redis 的工具、指南和教程
截图
-
查看Redis OM,用于将 Redis 用作多模型数据库的客户端库。
-
使用RedisInsight在 Redis 中可视化您的数据。
-
注册一个免费的 Redis 数据库.
结论
在这里,我们来到了应用程序的结尾,我们学习了如何使用 NodeJS 和 Redis OM 创建执行 crud 操作的 API,并通过存储 JSON 文档和检索它们来利用 RedisJson 功能和 RedisSearch 功能来执行查询和完整\ _文本_搜索。
更多推荐
所有评论(0)