JS: Веб-разработка

Теория: Создание CRUD на Fastify

Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к CRUD-операциям. В этом уроке мы познакомимся с ними подробнее.

Что такое CRUD

CRUD — это широко распространенный термин, означающий четыре стандартные операции над любой сущностью. Рассмотрим такой пример:

  • Создание (Create) — регистрация пользователя
  • Чтение (Read) — просмотр профиля пользователями сайта или в административном интерфейсе
  • Обновление (Update) — Обновление личных данных, смена электронной почты или пароля
  • Удаление (Delete) — удаление данных

Точно так же можно расписать действия над любыми другими ресурсами: фотографиями пользователя, его друзьями, сообщениями и так далее. Чтобы создать полный CRUD, нужно выполнить следующие действия:

  • Создать сущность в коде (как правило, это класс)
  • Добавить таблицу в базу
  • Написать тесты для проверки обработчиков
  • Добавить обработчики
  • Добавить шаблоны

Ниже мы пройдемся по всему процессу создания CRUD пользователя за исключением работы с базой данных и тестов.

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

МетодМаршрутШаблонОписание
GET/usersusers/index.pugСписок пользователей
GET/users/newusers/new.pugФорма создания нового пользователя
GET/users/
users/show.pugПрофиль пользователя
POST/usersСоздание нового пользователя
GET/users/
/edit
users/edit.pugФорма редактирования пользователя
PATCH/PUT/users/
Обновление пользователя
DELETE/users/
Удаление пользователя

Такое соглашение об именовании маршрутов изначально появилось во фреймворке Ruby On Rails, а затем его адаптировали во многих других. Здесь мы его используем из-за его универсальности и понятности.

Разберем основные маршруты и примеры обработчиков. Ниже код

import fastify from 'fastify';
import view from '@fastify/view';
import pug from 'pug';

const state = {
  users: [
    {
      id: 1,
      name: 'First User',
      email: 'first@user.com',
    },
    {
      id: 2,
      name: 'Second User',
      email: 'second@user.com',
    },
  ],
};

const app = fastify();
const port = 3000;

await app.register(view, { engine: { pug }, root: 'src/views' });

// Просмотр списка пользователей
app.get('/users', (req, res) => {
  const data = {
    users: state.users,
  };

  res.view('users/index.pug', data);
});

// Форма создания нового пользователя
app.get('/users/new', (req, res) => res.view('users/new.pug');

// Просмотр конкретного пользователя
app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  const user = state.users.find((item) => item.id === parseInt(id));
  if (!user) {
    res.code(404).send({ message: 'User not found' });
  } else {
    res.view('users/show.pug', { user });
  }
});

// Создание пользователя
app.post('/users', (req, res) => {
  const user = {
    name: req.body.name,
    email: req.body.email,
    password: req.body.password,
  };

  state.users.push(user);

  res.redirect('/users');
});

// Форма редактирования пользователя
app.get('/users/:id/edit', (req, res) => {
  const { id } = req.params;
  const user = state.users.find((item) => item.id === parseInt(id));
  if (!user) {
    res.code(404).send({ message: 'User not found' });
  } else {
    res.view('users/edit.pug', { user });
  }
});

// Обновление пользователя
app.patch('/users/:id', (req, res) => {
  const { id } = req.params;
  const { name, email, password, passwordConfirmation, } = req.body;
  const userIndex = state.users.findIndex((item) => item.id === parseInt(id));
  if (userIndex === -1) {
    res.code(404).send({ message: 'User not found' });
  } else {
    state.users[userIndex] = { ...state.users[userIndex], name, email };
    res.send(users[userIndex]);
    res.redirect('/users');
  }
});

// Удаление пользователя
app.delete('/users/:id', (req, res) => {
  const { id } = req.params;
  const userIndex = state.users.findIndex((item) => item.id === parseInt(id));
  if (userIndex === -1) {
    res.code(404).send({ message: 'User not found' });
  } else {
    state.users.splice(userIndex, 1);
    res.redirect('/users');
  }
});

app.listen({ port }, () => {
  console.log(`Example app listening on port ${port}`);
});

Create

  • Создание нового пользователя

Это действие осуществляется POST-запросами. Именно такие маршруты обрабатывают данные с форм.

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

Read

  • Просмотр профиля пользователями сайта
  • Просмотр пользователя в административном интерфейсе

Это действие осуществляется GET-запросами. В нашем примере это два маршрута:

  • GET /users — просмотр списка пользователей. В примере мы возвращаем массив users()
  • GET /users/:id — просмотр конкретного пользователя. В примере мы ищем пользователя по id и возвращаем его. Если пользователь не найден, возвращается код 404

Update

  • Обновление личных данных
  • Смена емейла
  • Смена пароля

Здесь используются глаголы PUT и PATCH, оба используются для обновления данных

  • PUT обычно используется для обновления всего объекта
  • PATCH используется, когда нужно обновить только часть данных объекта

В Fastify для каждого из этих глаголов определен свой метод.

В нашем примере мы получаем данные из формы req.body, затем ищем нужного пользователя по id. Если пользователь не найден, то возвращаем код 404. Если пользователь найден, то обновляем данные этого пользователя. Обратите внимание, что данные могут обновляться не все, поэтому в примере используется оператор рест { ...users[userIndex] }. В обработчике этого запроса могут быть дополнительные проверки на корректность данных.

Delete

  • Удаление

Для удаления используется глагол DELETE, в Fastify для него определен аналогичный метод.

В нашем примере, как и в других обработчиках, проверяем существование пользователя и удаляем, если пользователь существует. Если пользователя нет, возвращаем статус 404.

Точно так же можно расписать действия над любыми другими ресурсами, фотографиями пользователя, его друзьями, сообщениями и т.п.

Архитектура приложения

CRUD операции объединяют маршруты и их обработчики в логические блоки вокруг каких-то сущностей. Например, CRUD курсов, упражнений, уроков, статей в блоге и так далее. Такая структура, позволяет разбить приложение на файлы, так, чтобы, с ростом приложения, его было удобно поддерживать. В разработке на Fastify принято объединять маршруты и обработчики в отдельные файлы, каждый из которых связан с конкретной сущностью, а сами файлы располагать в директории routes:

.
└── routes
    ├── users.js
    ├── posts.js
    └── root.js

В примере выше каждый из роутов содержит логику связанную с конкретной сущностью:

  • users.js — маршруты для пользователей
  • posts.js — для постов
  • root.js — маршруты, не относящиеся к сущностям. Например, это может быть маршрут главной страницы

Завершено

0 / 23