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