Зарегистрируйтесь, чтобы продолжить обучение

Создание CRUD на Fastify JS: Веб-разработка

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

Что такое CRUD

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

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

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

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

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

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

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

Такое соглашение об именовании маршрутов изначально появилось во фреймворке 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 — маршруты, не относящиеся к сущностям. Например, это может быть маршрут главной страницы

Самостоятельная работа

  1. Выполните шаги из урока у себя на компьютере. Приведите маршруты в соответствие с таблицей в уроке. Выделите обработчики в отдельный контроллер и поправьте роутинг
  2. Сделайте то же самое для сущности курсов
  3. Залейте изменения на GitHub

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff