Несмотря на огромное число разнообразных сайтов, практически всю веб-разработку можно свести к 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 — маршруты, не относящиеся к сущностям. Например, это может быть маршрут главной страницы
Самостоятельная работа
- Выполните шаги из урока у себя на компьютере. Приведите маршруты в соответствие с таблицей в уроке. Выделите обработчики в отдельный контроллер и поправьте роутинг
- Сделайте то же самое для сущности курсов
- Залейте изменения на GitHub
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.