Создание Fullstack приложения с помощью NodeJS, ExpressJs и Redis-OM


Обзор моей работы

По мнению Мейка Викинга (автора книги «Искусство создавать воспоминания»), счастливые воспоминания очень важны для нашего психического здоровья. Они укрепляют наше чувство идентичности и цели и скрепляют наши отношения. Счастливые воспоминания — важный ингредиент настоящего счастья. Так родился мой проект — приложение Memories App, которое позволяет каждому задокументировать свои воспоминания в любой момент времени.

Без лишних слов перейдем к деталям проекта.

Преамбула
Redis — это база данных NoSQL, которую любят за ее простоту и скорость. В этом посте блога мы будем создавать Fullstack-приложение с базой данных Redis для хранения данных. Мы построим нашу модель с использованием возможностей Redis-OM, которые позволяют создавать, извлекать, обновлять и осуществлять полнотекстовый поиск.
Не страшно, если вы не знакомы с базой данных Redis.

Для начала:

Что такое Redis?
Redis — это хранилище ключевых значений в памяти, которое часто используется в качестве кэша, чтобы сделать традиционные базы данных быстрее. Однако он превратился в многомодельную базу данных, способную работать с полнотекстовым поиском, графовыми связями, нагрузками искусственного интеллекта и многим другим.

Что такое RedisJSON?
RedisJSON — это модуль Redis, который обеспечивает поддержку JSON в Redis. RedisJSON позволяет хранить, обновлять и извлекать значения JSON в Redis так же, как и любой другой тип данных 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, прежде чем перейдем к последней части appcreate .env в папке сервера и добавьте
PORT=3005
REDIS_HOST="redis://localhost:6379"

JWT_TOKEN_SECRET= "add your preferred jwt secret"
JWT_EXPIRE="add the expiry"
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Установив dotenv как часть наших зависимостей, мы можем начать использовать данные переменных окружения в нашем приложении.

Создание и подключение клиента Redis

  • Чтобы создать и подключить клиента Redis, создайте папку 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;
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Создайте модели/репозитории

  • Создайте папку 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.

Примечание: что касается типа поля, текстовое поле очень похоже на строку. Разница в том, что строковые поля могут быть сопоставлены только по полному значению — никаких частичных совпадений — и лучше всего подходят для ключей, в то время как текстовые поля имеют возможность полнотекстового поиска и оптимизированы для человекочитаемого текста.

  • Мы также создали репозиторий для каждого из них. Репозиторий — это основной интерфейс Redis OM. Он предоставляет нам методы для чтения, записи и удаления конкретной сущности

И наконец…

  • Мы создали индекс, чтобы иметь возможность искать данные в каждом хранилище. Для этого мы вызываем .createIndex(). Если индекс уже существует и он идентичен, эта функция ничего не сделает. Если же он отличается, она удалит его и создаст новый.

Настройка маршрутов

  • Создайте папку route и добавьте в нее файлы 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 validator, поэтому мы импортировали схему валидации постов и пользователей и обеспечили, чтобы наши данные сначала прошли валидацию, когда запрос отправляется на конечную точку. Вы можете получить доступ к необходимым файлам валидации здесь Файлы валидации и промежуточное ПО валидатора

Настройка файла 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 для подписи учетных данных и bycrypt для шифрования пароля перед сохранением в базе данных.

  • На маршруте /register мы будем:
    • Получать данные пользователя.
    • Проверим, существует ли уже пользователь.
    • Зашифруем пароль пользователя.
    • Создадим пользователя в нашей базе данных.
    • И, наконец, создадим подписанный 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 заменяет entityId, предоставленный нам Redis-OM, чтобы соответствовать традиционному ответу.

 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 (т.е. замените «M» в MEAN/MERN на «R» для Redis).

Краткое видео объяснение проекта:

Используемые технологии

  • JS/Node.js
  • Express Js
  • JSON Web Token
  • ReactJs, Redux
  • Материальный пользовательский интерфейс

Ссылка на код

phenom-world / Memories-RERN-App

Мерн-приложение с полным стеком под названием «Memories», где пользователи могут публиковать интересные события, происходящие в их жизни

Memories App

💫💫💫 Живая демонстрация 💫💫💫

Приложение «R «ERN» полного стека — от начала до конца.

Приложение называется «Воспоминания» и представляет собой простое приложение для социальных сетей, которое позволяет пользователям публиковать интересные события, произошедшие в их жизни. с реальными возможностями приложения, такими как аутентификация пользователей и социальный вход с использованием учетных записей Google и базы данных REDIS.

  • Redis — это высокопроизводительная база данных с открытым исходным кодом (с лицензией BSD), хранилище ключевых значений и структур данных в памяти, используемое в качестве базы данных, кэша и брокера сообщений. Redis имеет отличную производительность чтения-записи, высокую скорость чтения-записи IO из памяти и поддерживает частоту чтения-записи более 100k+ в секунду.

Обзорное видео (необязательно)

Вот короткое видео, которое объясняет проект и то, как в нем используется Redis:

Как это работает

  • Redis OM (произносится как REDiss OHM) позволяет легко добавить Redis в ваше приложение Node.js, отображая структуры данных Redis, которые вы знаете и любите, на…
Посмотреть на GitHub

Развертывание

Чтобы развертывание работало, вам необходимо создать бесплатный аккаунт на Redis Cloud

Heroku

Ссылка на развернутый бэкенд

Netlify

Frontend Memories App

Дополнительные ресурсы / информация

  • Redis-OM Node Github Repo

  • Redis OM для Node.js

  • Посмотрите это видео о преимуществах Redis Cloud перед другими провайдерами Redis

  • Redis Developer Hub — инструменты, руководства и учебники по Redis

Скриншоты




  • Ознакомьтесь с Redis OM, клиентскими библиотеками для работы с Redis как многомодельной базой данных.
  • Используйте RedisInsight для визуализации данных в Redis.
  • Зарегистрируйтесь для работы с бесплатной базой данных Redis.

Заключение

И вот мы подошли к концу приложения, в котором мы научились использовать NodeJS и Redis OM для создания API, выполняющих грубые операции и использующих возможности RedisJson для хранения документов JSON и их извлечения, а также возможности RedisSearch для выполнения запросов и полнотекстового_поиска.

Оцените статью
devanswers.ru
Добавить комментарий