我提交的概述
根据 meik wiking (《创造回忆的艺术》的作者),快乐的回忆对于我们的心理健康至关重要。他们增强了我们的身份和目标感,并结合了我们的关系。幸福的回忆是当前幸福的重要组成部分。因此,这赋予了我的项目,记忆应用程序,该应用程序使每个人都可以在任何时间点记录他们的记忆。
不进一步的ADO,让我们浏览项目详细信息。
序言
REDIS是一个NOSQL数据库,以其简单性和速度而被爱。在此Blogpost中,我们将构建一个带有REDIS数据库的Fullstack应用程序,用于存储数据。我们将使用Redis-OM功能构建模型,这些功能允许创建,检索,更新和执行全文搜索。
如果您不熟悉REDIS数据库,那就很好。
从:
开始 什么是redis?
好吧,Redis是一个内存的键值商店,通常用作缓存来更快地使传统数据库。但是,它已演变为一个多模型数据库,能够全文搜索,图形关系,AI工作负载等。
什么是redisjson?
Redisjson是一个REDIS模块,可在Redis中提供JSON支持。 REDISJSON让您的商店,更新和检索Redis中的JSON值,就像您使用任何其他REDIS数据类型一样。 Redisjson还与Redisearch无缝合作,让您索引和查询JSON文档。这是使用REDIS数据库的最佳方法。
什么是redis-om
redis om(发音为rediss ohm)是一个为redis提供对象映射的库,这就是om的代表...对象映射。它将重新数据类型映射到josscript对象。它使您可以搜索这些哈希和JSON文档。它使用redisjson和redisearch做到这一点。
redisjson添加了JSON文档数据类型和操纵它的命令时,Redisearch添加了各种搜索命令来索引JSON文档和哈希的内容。
让我们用redis构建,并占据这些redis-omm功能的优势!
配置
启动redis
首先,我们将建立我们的环境。然后,出于开发目的,我们将使用Docker运行REDIS堆栈:
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客户端,请在服务器文件夹中创建配置文件夹,然后创建一个名为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;
创建模型/存储库
- 在服务器文件夹中创建一个名为“模型”的文件夹,并分别添加名为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();
- 对于两个模式,我们都定义了一个实体。一个实体是与数据合作时保存您的数据的类 - 被映射到的东西。这是您创建,阅读,更新和删除的内容。任何扩展实体的类都是实体。
- 我们还为两者定义了我们的模式;一个模式定义了您的实体,它们的类型以及它们在内部映射到Redis上的字段。默认情况下,实体映射到JSON文档。
注意:在字段类型中,文本字段很像字符串。不同之处在于,字符串字段只能在其整个值上匹配 - 没有部分匹配 - 最适合钥匙,而文本字段在其上启用了全文搜索,并且对人类可读的文本进行了优化。 p>
- 我们还为每个存储库创建了一个存储库。存储库是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;
注意,验证器是通过明确验证器完成的,因此我们导入了帖子和用户验证模式,并确保在将请求发送到端点时首先验证我们的数据。您可以在此处访问必要的验证文件Validation Files和validator middleware
设置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客户端。
oops,我们要到达那里的同志!
设置控制器
这是我们应用程序逻辑所在的地方。对于用户注册,我们将使用JWT在将密码存储在我们的数据库中之前对凭据和Bycrypt进行加密。
- 从 /注册路线,我们将:
- 获取用户输入。
- 验证用户是否已经存在。
- 加密用户密码。
- 在我们的数据库中创建用户。
- 最后,创建一个签名的JWT令牌。
controller/user.js
export const signup = async (req, res) => {
const { firstName, lastName, email, confirmPassword } = req.body;
const existingUser = await userRepository.search().where("email").is.equalTo(email).return.first();
//check if user already registered with the email
if (existingUser) {
return res.status(400).json({ message: "A user already registered with the email." });
}
if (req.body.password !== confirmPassword) {
return res.status(400).json({ message: "Passwords don't match." });
}
//hash password
const hashedPassword = await bcrypt.hash(req.body.password, 12);
const user = await userRepository.createAndSave({ name: `${firstName} ${lastName}`, email, password: hashedPassword });
const token = jwt.sign({ email: user.email, id: user.entityId }, process.env.JWT_TOKEN_SECRET, {
expiresIn: process.env.JWT_EXPIRE,
});
const { entityId, password, ...rest } = user.toJSON();
const data = { id: user.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 } });
};
- ,我们会停下来请求我们的帖子端点。
redis云平台
- 在最终音符上,让我们浏览Redis Cloud Platform
- 在Redis上创建一个帐户并登录。
- 导航到仪表板以创建新的订阅。您可以从免费的层开始,然后根据您的应用程序使用来升级。
- 创建一个名为Memories应用的新数据库
请注意,端点是在准备与此项目一起准备的前端应用中消耗的
提交类别:
- Mean/Mern Maverick :重新作为主要数据库,而不是MongoDB(即用REDIS代替平均值/Mern)。
该项目的简短视频说明:
使用的技术
- js/node.js
- Express JS
- JSON Web令牌
- 材料UI
链接到代码
phenom-world / Memories-RERN-App
一个完整的堆栈MERN应用程序,称为“记忆”,用户可以在其中发布有趣的事件
Memories App
? ? ? Live Demo ^« d« ð«
完整堆栈“ r” ern应用程序 - 从头到尾。
该应用程序称为“记忆”,它是一个简单的社交媒体应用程序,允许用户发布生活中发生的有趣事件。使用Google帐户和 REDIS数据库。
- REDIS是一种开源(BSD许可),高性能键值数据库和内存数据结构存储存储,用作数据库,缓存和消息代理。 REDIS具有出色的读写性能,来自内存的快速io读取速度,并支持每秒超过100k +的读写频率。
概述视频(可选)
这是一个简短的视频,解释了该项目及其使用方式:
它如何工作
- redis om(发音为rediss ohm)使您可以轻松地将redis添加到您的node.js应用程序中,通过映射您知道并喜欢的redis数据结构,
部署
要使部署工作,您需要在Redis Cloud上创建免费帐户
Heroku
Netlify
其他资源 /信息
Watch this video on the benefits of Redis Cloud over other Redis providers
Redis Developer Hub - tools, guides, and tutorials about Redis
屏幕截图
- 查看Redis OM,客户库,用于与Redis合作作为多模型数据库。
- 使用RedisInsight在Redis中可视化您的数据。
- 注册free Redis database。
结论
,我们来到了应用程序的尽头,我们已经学会了如何使用nodejs和redis om来创建执行CRUD操作的API,并通过存储JSON文档并检索它们以及重新搜索功能来执行查询,从而利用Redisjson功能来实现REDISJSON功能和full_text_search。