👾
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
  • Створення користувача
  • Отримання всіх користувачів
  • Отримання користувача за id
  • Рефакторинг коду (виносимо повторювану дію в middleware)
  • Зміна даних користувача за id
  • Видалення користувача за id
  1. Теорія
  2. Фреймворк Express

CRUD

PreviousРоутінгNextНалаштування лінтера

Last updated 1 year ago

Пройдемо на практиці найпоширеніші http-методи. Але спочатку проведемо деякі налаштування для коректної роботи.

Спершу створимо middleware, який дозволить нам отримувати дані з req.body. Якщо цей middleware не прописати, то нам буде з body приходити undefined, бо за замовчанням ми не маємо доступу до цих даних.

app.use(express.json());

Створимо проєкт такої структури:

project
      ├── index.js
      └── usersdb.json

У файлі index.js будемо писати логіку нашого сервера. Поки ми не працюємо із базою даних у її ролі виступатиме файл usersdb.json. Поки що це порожній масив. А зберігатимемо в нього обʼєкти наших юзерів.

usersdb.json
[]

Створимо аналог сервісу REST API:

Метод
Маршрут
Операція

POST

/users

Створити користувача

GET

/users

Отримати всіх користувачів

GET

/users/<user_id>

Отримати користувача за id

PUT/PATCH

/users/<user_id>

Оновити користувача за id

DELETE

/users/<user_id>

Видалити користувача за id

Для генерування id юзера встановимо бібліотеку генерування унікального id .

terminal
npm i uuid

Зберемо кістяк нашого сервера на який будемо нарощувати функціонал.

index.js
// ===========LIBS=========== //
const express = require("express");
const uuid = require("uuid").v4;
const fs = require("fs").promises;

// ===========EXPRESS APP=========== //
const app = express();

// ===========MIDDLEWARE=========== //
app.use(express.json());
// Тут будемо писати наші кастомні middleware

// ===========CONTROLLERS=========== //
// Тут будемо писати наші контроллери

// ===========SERVER INIT=========== //
app.listen(3000, () => {
  console.log("server is up and runned on port 3000");
});

Контроллери це по суті наші функції обробники запитів від користувача. В залежності від типу запиту і маршруту, якщо сервер виявляє збіг, то буде виконуватися ця функція.

У найпростішому розумінні наш контроллер повинен виконати такі дії:

  • провести валідацію даних, які прийшли

  • створити (укомплектувати) дані для подальшої обробки

  • опрацювати (синхронізувати) ці дані з базою даних

  • повернути відповідь на front-end

На цьому етапі для простоти розуміння пропустимо пункт валідації, вважаючи, що дані від користувача приходять валідні. Розглянемо пʼять вищезгаданих контролерів.

Створення користувача

Кожне звернення до бази даних, або роботи з файлами - це асинхронна операція, тому колбек-функція всередині контроллера має бути async. Крім того, використовуємо конструкцію try{} catch(){} для відловлювання помилок. Якщо виникає помилка, то можна вивести в консоль помилку і повернути на фронтенд статус 500 (свого роду збій на сервері).

Щодо основного коду:

  • Спочатку витягнемо отримані дані з об'єкта req його параметра body. В цьому обʼєкті передаються дані з фронтенду.

  • Створюємо новий обʼєкт користувача із отриманими даними і генеруємо унікальний id

  • Читаємо файл бази даних, парсимо його, пушимо нового користувача, і знову записуємо назад у файл

  • Формуємо відповідь для фронтенду. Передаємо статус 201 (успішна операція), а також json із повідомленням і обʼєктом новоствореного користувача.

index.js
/**
 * CREATE USER: POST /users    
 */

app.post("/users", async (req, res) => {
  try {
    const { name, year, email } = req.body;
    // тут має бути валідація даних, які прийшли

    //створюємо обʼєкт нового користувача, генеруємо для нього унікальний id
    const newUser = { name, year, email, id: uuid() };

    // зберігаємо користувача в базу даних (usersdb.json)
    const usersDB = await fs.readFile("./usersdb.json");
    const users = JSON.parse(usersDB);
    users.push(newUser);
    await fs.writeFile("./usersdb.json", JSON.stringify(users));

    //надсилаємо відповідь на фронтенд
    res.status(201).json({
      msg: "User created",
      user: newUser,
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Перевіримо роботу нашого коду. Зробимо запит у програмі Postman (стрілками вказано порядок введення налаштувань):

Після натискання на кнопку Send ми отримуємо від нашого сервера відповідь:

Відкриємо наш файл usersdb.json, який виступає у ролі бази даних і переконуємося, що дані збережено:

Отримання всіх користувачів

Для отримання даних з сервера використовують метод GET.

index.js
/**
 * GET USERS LIST: GET /users
 */

app.get("/users", async (req, res) => {
  try {
    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    res.status(200).json({ msg: "Success", users });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Для перевірки роботи логіки додамо ще одного користувача і в окремій вкладці зробимо GET-запит.

Отримання користувача за id

Якщо у нашому запиті передається змінний параметр як-то <user_id>, то у контроллері в маршруті він вказується після двох крапок. І отримати цей параметр ми можемо взявши його зі змінної з такою ж назвою у обʼєкті req.params. Щоб отримати обʼєкт користувача за унікальним id, треба перебрати масив методом find.

index.js
/**
 * GET ONE USER BY ID: GET /users/<user_id>
 */

app.get("/users/:userId", async (req, res) => {
  try {
    const { userId } = req.params;
    const users = JSON.parse(await fs.readFile("./usersdb.json"));

    const user = users.find((item) => item.id === userId);

    res.status(200).json({
      msg: "Success",
      user,
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Знаючи унікальний id користувача ми можемо отримати з бази даних інформацію про нього. Відкриємо нову вкладинку в Postman і виконаємо запит.

Рефакторинг коду (виносимо повторювану дію в middleware)

З таблички вище видно, що нам доведеться принаймні в кількох контролерах отримувати обʼєкт користувача за id. Щоб не дублювати код, доречно винести цей функціонал в middleware і він вже далі передаватиме цей обʼєкт користувача до наступного обробника.

Напишемо кастомний middleware:

Спільною властивістю для таких запитів є маршрут ("/users/:userId"). Тобто ця проміжна функція відпрацює, якщо буде збігатися маршрут.

Якщо збігу користувача за id не буде виявлено, то повернемо помилку 404, якщо користувача з таким id буде виявлено, то middleware передасть в req.user обʼєкт нашого користувача і передасть обробку наступній функції.

index.js
/**
 * Сustom middleware: find user by id
 */
app.use("/users/:userId", async (req, res, next) => {
  try {
    const { userId } = req.params;
    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    const user = users.find((item) => item.id === userId);
    if (!user) {
      return res.status(404).json({
        msg: "User does not exist",
      });
    }
    req.user = user;
    next();
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Проведемо рефакторинг нашого контролера. У ньому вже не потрібно звертатися до бази даних для отримання обʼєкта користувача, бо він міститься в req.user, тому і функцію можна робити не асинхронною і не використовувати try{} catch(){}.

index.js
/**
 * GET ONE USER BY ID: GET /users/<user_id>
 */

app.get("/users/:userId", (req, res) => {
  const { user } = req;

  res.status(200).json({
    msg: "Success",
    user,
  });
});

Перевіримо запити за id, який не існує і який існує:

Зміна даних користувача за id

Тут хоч і проходить middleware і повертає нам обʼєкт користувача, але функцію все одно треба робити асинхронною, бо у нас відбувається звернення до бази даних (запис файлу).

Проядок коду:

  • Спочатку створюємо оновлений обʼєкт користувача. Розпиляємо старий обʼєкт і отримані оновлені дані від фронтренду з req.body.

  • Витягаємо з бази даних (файлу) всіх користувачів. Створюємо новий масив і проходимо методом map старий масив. Якщо id не збігається - лишаємо старе значення, якщо id збігається - повертаємо оновлений обʼєкт користувача.

  • Перезаписуємо файл бази даних новим масивом

  • Повертаємо фронтенду відповідь

index.js
/**
 * UPDATE USER BY ID: PATCH /users/<user_id>
 */
app.patch("/users/:userId", async (req, res) => {
  try {
    const { user } = req;
    const { name, year, email } = req.body;
    const updatedUser = { ...user, name, year, email };

    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    let updatedUsers = [];
    updatedUsers = users.map((item) => {
      if (item.id !== updatedUser.id) return item;
      if (item.id === updatedUser.id) return updatedUser;
    });

    await fs.writeFile("./usersdb.json", JSON.stringify(updatedUsers));

    res.status(200).json({
      msg: "Success",
      user: updatedUser,
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Змінимо дані якогось користувача за унікальним id:

Перевіримо чи змінились дані у базі даних (файлі):

Видалення користувача за id

Тут так само асинхронна функція, тому що ми звертаємося до файлу. Тут використовуємо метод filter. Відбираємо у новий масив всіх користувачів у яких не збігається id. І перезаписуємо файл. Таким чином збережуться всі користувачі, крім того, чий id прийшов з фронтенду.

index.js
/**
 * DELETE USER BY ID: DELETE /users/<user_id>
 */
app.delete("/users/:userId", async (req, res) => {
  try {
    const { user } = req;

    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    let updatedUsers = [];
    updatedUsers = users.filter((item) => item.id !== user.id);

    await fs.writeFile("./usersdb.json", JSON.stringify(updatedUsers));
    // як варіант res.sendStatus(204);
    res.status(200).json({
      msg: "Success",
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

Перевіримо як проходить запит:

Перевіримо чи видалився контакт з файлу:

Як бачимо, всі контролери працюють. Елементарний REST API сервіс побудовано.

Весь код розглянутого сервера
index.js
// ===========LIBS=========== //
const express = require("express");
const uuid = require("uuid").v4;
const fs = require("fs").promises;

// ===========EXPRESS APP=========== //
const app = express();

// ===========MIDDLEWARE=========== //
app.use(express.json());

/**
 * Сustom middleware: find user by id
 */
app.use("/users/:userId", async (req, res, next) => {
  try {
    const { userId } = req.params;
    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    const user = users.find((item) => item.id === userId);
    if (!user) {
      return res.status(404).json({
        msg: "User does not exist",
      });
    }
    req.user = user;
    next();
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

// ===========CONTROLLERS=========== //

/**
 * CREATE USER: POST /users
 */
app.post("/users", async (req, res) => {
  try {
    const { name, year, email } = req.body;
    // тут має бути валідація даних, які прийшли

    //створюємо обʼєкт нового користувача, генеруємо для нього унікальний id
    const newUser = { name, year, email, id: uuid() };

    // зберігаємо користувача в базу даних (usersdb.json)
    const usersDB = await fs.readFile("./usersdb.json");
    const users = JSON.parse(usersDB);
    users.push(newUser);
    await fs.writeFile("./usersdb.json", JSON.stringify(users));

    //надсилаємо відповідь на фронтенд
    res.status(201).json({
      msg: "User created",
      user: newUser,
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

/**
 * GET USERS LIST: GET /users
 */
app.get("/users", async (req, res) => {
  try {
    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    res.status(200).json({ msg: "Success", users });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

/**
 * GET ONE USER BY ID: GET /users/<user_id>
 */
app.get("/users/:userId", (req, res) => {
  const { user } = req;

  res.status(200).json({
    msg: "Success",
    user,
  });
});

/**
 * UPDATE USER BY ID: PATCH /users/<user_id>
 */
app.patch("/users/:userId", async (req, res) => {
  try {
    const { user } = req;
    const { name, year, email } = req.body;
    const updatedUser = { ...user, name, year, email };

    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    let updatedUsers = [];
    updatedUsers = users.map((item) => {
      if (item.id !== updatedUser.id) return item;
      if (item.id === updatedUser.id) return updatedUser;
    });

    await fs.writeFile("./usersdb.json", JSON.stringify(updatedUsers));

    res.status(200).json({
      msg: "Success",
      user: updatedUser,
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

/**
 * DELETE USER BY ID: DELETE /users/<user_id>
 */
app.delete("/users/:userId", async (req, res) => {
  try {
    const { user } = req;

    const users = JSON.parse(await fs.readFile("./usersdb.json"));
    let updatedUsers = [];
    updatedUsers = users.filter((item) => item.id !== user.id);

    await fs.writeFile("./usersdb.json", JSON.stringify(updatedUsers));
    // як варіант res.sendStatus(204);
    res.status(200).json({
      msg: "Success",
    });
  } catch (err) {
    console.log(err);
    res.sendStatus(500);
  }
});

// ===========SERVER INIT=========== //
app.listen(3000, () => {
  console.log("server is up and runned on port 3000");
});

Коди статус-повідомлень:

Інформаційні 100 – 199

Успішні 200 – 299

Перенаправлення 300 - 399

Клієнтські помилки 400 – 499

Серверні помилки 500 - 599

Коди статус-повідомлень не обовʼязково знати всі на памʼять. Але Бажано основні знати:

200 - OK. Запит успішно опрацьовано.

201 - Created. Запит успішно виконано і в результаті було створено ресурс.

400 - Bad Request. Сервер не розуміє запит через неправильний синтаксис.

401 - Unauthorized. Для отримання запитаної відповіді потрібна автентифікація.

403 - Forbidden. У клієнта немає прав доступу до вмісту, тому сервер відмовляється дати відповідь.

404 - Not Found. Сервер не може знайти запитуваний ресурс.

500 - Internal Server Error. Сервер зіштовхнувся із ситуацією, яку він не знає як обробити.

Покликання:

📚
3️⃣
uuid
CRUD
Коди відповідей http