👾
Node.js
  • 🧑‍💻Full-Stack Web Developer
  • 📚Теорія
    • 1️⃣Основи Node.js
      • Вступ
      • Модулі Node.js
      • Запуск скриптів модулів в Node.js
      • Структура проєкту, експорт-імпорт, index.js як хаб
      • Модулі CommonJS
      • Модулі MJS
      • Модулі ECMAScript
      • Модулі NPM + базові модулі
      • Глобальні змінні
      • Робота з файлами
    • 2️⃣Консольні додатки
      • Створення консольних додатків
    • 3️⃣Фреймворк Express
      • Про Express
      • Nodemon і запуски скриптів
      • Postman
      • Проміжне ПЗ middleware
      • Передача даних на сервер
      • Роутінг
      • CRUD
      • Налаштування лінтера
    • 4️⃣REST API
      • Змінні оточення
      • Логування
      • REST
      • Методи HTTP
      • CORS
      • Формування URL для REST API
      • Контроллери відсутнього роуту і непередбачуваної помилки
      • Валідація даних Joi
      • Рефакторинг додатку за MVC архітектурою
      • Express автогенератор додатку
    • 5️⃣База даних Mongo.DB
      • Основи MongoDB
      • Налаштування Mongo Atlas
      • Встановлення локальної MongoDB і основні команди
    • 6️⃣ODM Mongoose
      • Mongoose
      • Порядок планування бекенд додатку
      • чорнетка
    • 7️⃣Автентифякація WJT
      • чорнетка
      • чорнетка 2
    • 8️⃣Файли
      • чернетка
    • 9️⃣тестування
      • чернетка
    • 🔟Page 14
      • імейли
    • чорнетка докер
    • чорнетка сокети
    • додаткові матеріали
    • 👷Практика
      • 1️⃣Page 4
      • 2️⃣Page 5
      • 3️⃣Page 6
      • 4️⃣Page 7
      • 5️⃣Page 8
      • 6️⃣Page 9
  • Про мене
    • Про мене
Powered by GitBook
On this page
  1. Теорія
  2. Автентифякація WJT

чорнетка 2

Рілейшени

one to many

many to many

many to one

Розглянемо на прикладі onе to many

створимо список тудущек для цього створимо схему

models/todoModel.js
const { model, Schema, Types } = require("mongoose");

const todoSchema = new Schema(
  {
    title: {
      type: String,
      required: true,
      maxLength: 20,
      trim: true,
    },
    description: {
      type: String,
      maxLength: 100,
    },
    due: {
      type: Date,
      required: true,
    },
    completed: {
      type: Boolean,
      default: false,
    },
    owner: {
      type: Types.ObjectId,
      ref: "User",
      required: [true, "Todo must have an owner.."],
    },
  },
  {
    timestamps: true,
    versionKey: false,
  }
);

const Todo = model("Todo", todoSchema);

module.exports = Todo;

В цьому прикладі із нового це owner. Тип має буди типом айдіщки. реф вказуємо назву колекції в якій якраз зьерігаються онери тобто користувачі.

тобто треба подивитися назву яку ми вказаои в моделі в юзерах.

required означає, що це обов'язковий параметр для тудушки і він не може бути нічийним.

для тудушек створимо окремі роути

routes/todoRoutes.js
const { Router } = require("express");

const { createTodo, getTodosList } = require("../controllers/todoController");
const { protect } = require("../middlewares/authMiddlewares");

const router = Router();

router.use(protect);

router.post("/", createTodo);
// router.get("/", getTodosList);

module.exports = router;

тепер створимо контрололери тудушек

controllers/todoController.js
const { catchAsync } = require("../utils");
const todoService = require("../services/todoService");
const Todo = require("../models/todoModel");

exports.createTodo = catchAsync(async (req, res) => {
  const { title, description, due } = req.body;
  const newTodoData = { title, description, due, owner: req.user };

  const newTodo = await Todo.create(newTodoData);

  res.status(201).json({
    todo: newTodo,
  });
});

але роботу з моделлю можна витягнути в окремий сервіс.

services/todoService.js
const userRolesEnum = require("../constants/userRolesEnum");
const Todo = require("../models/todoModel");

exports.createTodo = (newTodoData, owner) => {
  const { title, description, due } = newTodoData;

  return Todo.create({
    title,
    description,
    due,
    owner,
  });
};

І тоді контролер матиме такий вигляд:

controllers/todoController.js
const { catchAsync } = require("../utils");
const todoService = require("../services/todoService");

exports.createTodo = catchAsync(async (req, res) => {
  const newTodo = await todoService.createTodo(req.body, req.user);

  res.status(201).json({
    todo: newTodo,
  });
});

і не забуваємо підключити роути в індекс файлі

index.js
const todoRoutes = require("./routes/todoRoutes");

app.use("/todos", todoRoutes);

але для цього потрібно внести ля запиту наш токен, бо спрацьовує перевірка мілдвар

відправляємо

{
    "title": "123",
    "description": "456",
    "due": "2023-03"
}

отримуємо відповідь

{
    "todo": {
        "title": "123",
        "description": "456",
        "due": "2023-03-01T00:00:00.000Z",
        "completed": false,
        "owner": {
            "_id": "64c636290f9fbb9cf5c5a9bd",
            "name": "Oleksandr4",
            "email": "khomiak4@gmail.com",
            "year": 1986,
            "role": "user",
            "createdAt": "2023-07-30T10:06:33.189Z",
            "updatedAt": "2023-07-30T10:06:33.189Z"
        },
        "_id": "64cbed716fcaecb2ebba390b",
        "createdAt": "2023-08-03T18:09:53.492Z",
        "updatedAt": "2023-08-03T18:09:53.492Z"
    }
}

у нас повертає всього owner, але якщо вказати в контролері (todoService) owner.id, то повернеться тільки id власника

  1. Створимо контролер для отримання тудушек

от сам контролер обробника отримання тудужег

controllers/todoController.js
exports.getTodosList = catchAsync(async (req, res) => {
  const todos = await todoService.getTodosList();
  res.status(200).json({
    todos,
    total: todos.length,
  });

а от сервіс

services/todoService.js
exports.getTodosList = async () => {
  const todos = await Todo.find().populate("owner");
  return todos;
};

тотал - кількість результатів

опція populate - дає мождивість повернути розгорнуто оунера але в моделі має бути ref навпроти того поля

const todosQuery = await Todo.find(findOptions).populate(path: "owner", select: 'name email role');

але можна повенрути оунера окремо вкінці

controllers/todoController.js

  res.status(200).json({
    todos,
    total: todos.length,
    owner: req.user,
  });

а попюдейт прибрати

services/todoService.js
exports.getTodosList = async () => {
  const todos = await Todo.find();
  return todos;
};

КВЕРІ БІЛДЕР!

зазвичай виносять в окремий сервіс окремий клас

Запит пагінації з фронтенда передаватиметься як квері рядок

в постмені є квері парамс

і отримання переданих параметрів квері із рядка добиться черех req.query.

$regex - сталі комбінації. можна почитати в техдокументації.

Треба в базу даних додати побільше тудушек З різними значеннями полів щоб відпрацювати пошук

{
    "title": "cookies",
    "description": "456",
    "due": "2023-03"
}

ЗРОБИМО ПОШУК ПО TITLE AБО DESCRIPTION

controllers/todoController.js
exports.getTodosList = catchAsync(async (req, res) => {
  const todos = await todoService.getTodosList(req.query);
  res.status(200).json({
    todos,
    total: todos.length,
  });
});

services/todoService.js
exports.getTodosList = async (options) => {
  const findOptions = { title: { $regex: options.search } };

  const todos = await Todo.find(findOptions);
  return todos;
};

Пошук відбувається чітко по збігу слова (пошук відбувався суто по тайтлу)

Щоб прописати, щоб регістр не врливав на видачу результатів, тобто , щоб і великими і малими літерами повертадо рещультат є

services/todoService.js
  const findOptions = { title: { $regex: options.search, $options: "i" } };

якщо нап мотрібно зробити пощук на збіг і в полі тайтл і в полі дескріпшн, то пишемо використовуючи такі оператори

services/todoService.js
  const findOptions = {
    $or: [
      { title: { $regex: options.search, $options: "i" } },
      { description: { $regex: options.search, $options: "i" } },
    ],
  };

але якщо ми в search не передамо нічого, то у нас буде помилка сервера. у цьому разі потрібно зробити умову, якшо search прийшло то шукаємо за заданим пошуком, якшо немає, то повертаємо всі варіанти

services/todoService.js
 const findOptions = options.search
    ? {
         $or: [
          { title: { $regex: options.search, $options: "i" } },
           { description: { $regex: options.search, $options: "i" } },
         ],
       }
     : {};

Зараз повертаються усі тудушки навіть чужі, тому треба робити вибілку тільки серед своїх тудушек

Для цього ми з параметром oblect в сервіси передамо це параметр юзера, щоб робити вибілку по цьому юзеру

controllers/todoController.js
exports.getTodosList = catchAsync(async (req, res) => {
  const todos = await todoService.getTodosList(req.query, req.user);
  res.status(200).json({
    todos,
    total: todos.length,
  });

можна найпростіше зробити owner: user

services/todoService.js
exports.getTodosList = async (options, user) => {
  const findOptions = options.search
    ? {
        $or: [
          { title: { $regex: options.search, $options: "i" }, owner: user },
          { description: { $regex: options.search, $options: "i" }, owner: user },
        ],
      }
    : {};

  const todos = await Todo.find(findOptions);
  return todos;
};

А можна після задання пошукових опцій перевірити для кожної тудушки чи це цього користувача запис і якщо да, то і якщшо це юзер передати користувача його айді і тоді вибілка буде тільки бо його тудузках (модератор і адмін матиме доступ до всіх тудушек)

services/todoService.js
exports.getTodosList = async (options, user) => {
  const findOptions = options.search
    ? {
        $or: [
          { title: { $regex: options.search, $options: "i" } },
          { description: { $regex: options.search, $options: "i" } },
        ],
      }
    : {};

  if (options.search && user.role === userRolesEnum.USER) {
    findOptions.$or.forEach((searchOption) => {
      searchOption.owner = user;
    });
    // for (const searchOption of findOptions.$or) searchOption.owner = user;
  }

  if (!options.search && user.role === userRolesEnum.USER) {
    findOptions.owner = user;
  }

  const todos = await Todo.find(findOptions);
  return todos;
};\

ТЕПЕР СОРТУВАННЯ по параметру і ордеру

(по хорошому на ртримані квері дані теж треба прописувати валідатор, щоб не приходило підозрідих даних - так само як і валідували req.body так само валідуємо req.query)

в квері передаватимемо sort - за яким принципом будемо сортувати (передаємо туди поле бази даних за яким будем осортувати) наприклад title, description...

другий параметр order (від більшого до меншого чи від меншого до більшого DESC і ASC)

методи сортування вказують лдразу після find і вказуємо філди по яких сортуємо наприклад:

const todos = await Todo.find(findOptions).sort("title");

якщо зочемо в зворотньому напрямку сортувати то перед назвою поля ставоять знак мінуса

const todos = await Todo.find(findOptions).sort("-title");

Але в якості параметра сортування можна передати динамічні змінні

тобто, якшо отримуэмо DESC, то ставимо мінус і сортуємо за отриманим полем, якщо поле не передали, то за замовчанням візьмемо title.

  const todos = await Todo.find(findOptions).sort(
    `${options.order === "DESC" ? "-" : ""}${options.sort || "title"}`
  );

Гет запит наприкдаж може бути такий

порефакторимо цей код

примітка решультат пошуку виконується тільки після вказування await

тому код запиту можна розписати так

  const todosQuery = Todo.find(findOptions);

  todosQuery.sort(
    `${options.order === "DESC" ? "-" : ""}${options.sort || "title"}`
  );

  const todos = await todosQuery;

Це зробили для того, щоб вписувати додаткові параметри запиту

ПАГІНАЦІЯ

пашінація робиться за допомогою limit (скільки повернути - кількість) і skip (скільки пропускаємо після першого результату наприклад якщо 10 то 10 пропустить і з 11 буде видавати(це не індекс, а кількість))

проразуємо формули (отримуємо з квері номер сторінки запиту і макс кількість, яка має повернутися з бекенду)

Наприклад: ліміт 10. це означає, щ о при першій сторінці потрібно пропустити 0 документів, при друшій сторінці -пропустити 10 документів, третя сторінка - 20 документів. (page 1 = skip 0, page 2 = skip 10, page 3 = skip 20)

з опцій отримаємо номер сторінки запиту, якщо параметр не переданий, то за замовчанням сторінка 1. + означає стріншу перетворити в номер. використано тернарник. за такою ж аналогією отримуємо кількість результатів в запиті за замовчанням 5. Виразуємо формуду для скіп:

   const paginationPage = options.page ? +options.page : 1;
  const paginationLimit = options.limit ? +options.limit : 5;
   const skip = (paginationPage - 1) * paginationLimit;

Тепер окремо пропишемо наші параметри запиту

todosQuery.skip(skip).limit(paginationLimit);

і тільки потім запит

const todos = await todosQuery;

і от наприклад повний запит

Повертати також треба загалну кількість результатів, щоб на фронтенді мошли коректно розрахувати кількість сторінок і кількість пагінацій.

 const total = await Todo.count(findOptions);

і тоді у наш контролео уже треба повертати не просто todos, а обʼєкт результатів і повної кількості збігів

 return { todos, total };

Відповідно потрібно і переробити контролер

 const { todos, total } = await todoService.getTodosList(req.query, req.user);

 res.status(200).json({
   todos,
   total,
   owner: req.user,
 });

PreviousчорнеткаNextФайли

Last updated 1 year ago

Тепер якшо відправити пост запит

вищезазначений код витягає дані всіх тудушек за шдяхом гет

Тепер опигемо пошук за певним критерієм переданим в квері гет

📚
7️⃣
http://localhost:3000/todos
http://localhost:3000/todos
https://www.mongodb.com/docs/manual/reference/operator/query/regex/
http://localhost:3000/todos?search=cookie
http://localhost:3000/todos?order=DESC&sort=description
http://localhost:3000/todos?search=cookie&order=DESC&sort=description&limit=3&page=1