使用nodejs,expressjs和redis-om构建全栈应用程序
#node #redis #redishackathon #fullstack

我提交的概述

根据 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文档。

注意:在字段类型中,文本字段很像字符串。不同之处在于,字符串字段只能在其整个值上匹配 - 没有部分匹配 - 最适合钥匙,而文本字段在其上启用了全文搜索,并且对人类可读的文本进行了优化。

  • 我们还为每个存储库创建了一个存储库。存储库是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 Filesvalidator 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测试端点,成功注册后我们将获得以下响应。

Signup response

在整个应用程序中,请注意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测试端点,成功登录后我们将获得以下响应。

Login Response

ð,用户可以开始创建他或她的记忆

好吧,让我们潜入邮政控制器...

创建帖子

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 } });
};

Create post response

获取分页帖子

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) });
};

Get Paginated posts

获得帖子

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 } });
};

get a post

删除帖子

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" });
};


Delete Post response

像帖子一样

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 } });
};

Like Post Response

  • ,我们会停下来请求我们的帖子端点。

redis云平台

  • 在最终音符上,让我们浏览Redis Cloud Platform
  • Redis上创建一个帐户并登录。
  • 导航到仪表板以创建新的订阅。您可以从免费的层开始,然后根据您的应用程序使用来升级。
  • 创建一个名为Memories应用的新数据库 Create database

Dashboard

请注意,端点是在准备与此项目一起准备的前端应用中消耗的

提交类别:

  • Mean/Mern Maverick :重新作为主要数据库,而不是MongoDB(即用REDIS代替平均值/Mern)。

该项目的简短视频说明:

使用的技术

  • js/node.js
  • Express JS
  • JSON Web令牌
  • reactjs,redux
  • 材料UI

链接到代码

GitHub logo phenom-world / Memories-RERN-App

一个完整的堆栈MERN应用程序,称为“记忆”,用户可以在其中发布有趣的事件

Memories App

???   Live Demo ð«

完整堆栈“ r” ern应用程序 - 从头到尾。

该应用程序称为“记忆”,它是一个简单的社交媒体应用程序,允许用户发布生活中发生的有趣事件。使用Google帐户和 REDIS数据库

  • REDIS是一种开源(BSD许可),高性能键值数据库和内存数据结构存储存储,用作数据库,缓存和消息代理。 REDIS具有出色的读写性能,来自内存的快速io读取速度,并支持每秒超过100k +的读写频率。

概述视频(可选)

这是一个简短的视频,解释了该项目及其使用方式:

Embed your YouTube video

它如何工作

  • redis om(发音为rediss ohm)使您可以轻松地将redis添加到您的node.js应用程序中,通过映射您知道并喜欢的redis数据结构,

部署

要使部署工作,您需要在Redis Cloud上创建免费帐户

Heroku

Deployed Backend Link

Netlify

Frontend Memories App

其他资源 /信息

屏幕截图

Image1

Image2
Image3
Image4
Image5

结论

,我们来到了应用程序的尽头,我们已经学会了如何使用nodejs和redis om来创建执行CRUD操作的API,并通过存储JSON文档并检索它们以及重新搜索功能来执行查询,从而利用Redisjson功能来实现REDISJSON功能和full_text_search。