- Список пользователей
- Один пользователь
- Создание пользователя
- Обновление пользователя
- Удаление пользователя
- Заключение
Теперь, когда проект настроен, база данных подключена, и вы получили общее представление о современной REST-архитектуре, настало время перейти к практике. В этом уроке мы сосредоточимся на создании простых API, чтобы закрепить теоретические знания на конкретных примерах. Рассмотрим работу с ресурсом User и реализуем CRUD-операции (Create, Read, Update, Delete) для него.
Список пользователей
Начнем с реализации эндпоинта для получения списка всех пользователей. Этот эндпоинт будет располагаться по адресу /api/users. Для этого создадим файл routes/api/users.js со следующим содержимым:
import { asc } from 'drizzle-orm'
import * as schemas from '../../db/schema.js'
export default async function (fastify) {
const db = fastify.db
fastify.get(
'/users',
async function (request) {
const users = await db.query
.users
.findMany({
orderBy: asc(schemas.users.id),
})
return users
},
)
}
Разберем код подробнее:
- Fastify автоматически добавляет префикс /api в адрес, так как это файл находится внутри директории routes/api.
- Fastify автоматически преобразует возвращаемый объект в JSON и отдает его клиенту с выставленными заголовками. По умолчанию код ответа 200.
Проверим, что все работает:
Запустим сервер в отдельной вкладке
npm run dev > fastify start -w -l info -P app.js [21:01:55.683] INFO (47440): Server listening at http://127.0.0.1:3000 [21:01:55.685] INFO (47440): Server listening at http://[::1]:3000
Выполним запрос на /api/users с помощью
curl
. Запрос вернет записи, описанные в файле с сидами. Каждый перезапуск сервера будет возвращать разные данные, так как внутри используется faker для их генерации.# node-jq форматирует вывод для более красивого представления curl localhost:3000/api/users | npx node-jq [ { "id": 1, "fullName": "Miss Olivia Johns", "email": "Jeffery_Harris@yahoo.com", "updatedAt": null, "createdAt": "1724980072" }, { "id": 2, "fullName": "Lance Beer", "email": "Lew_Hoeger@hotmail.com", "updatedAt": null, "createdAt": "1724980072" } ]
В реальных приложениях часто требуется ограничить количество возвращаемых записей, например, при выводе длинных списков. Для этого используется постраничная навигация, которую можно реализовать с помощью параметров LIMIT и OFFSET в SQL-запросе.
fastify.get(
'/users',
async function (request) {
// Количество элементов на страницу
const perPage = 1
// Текущая страница со значением по умолчанию
const { page = 1 } = request.query
const users = await db.query
.users
.findMany({
orderBy: asc(schemas.users.id),
limit: perPage, // количество записей на страницу
offset: (page - 1) * perPage, // текущая страница
})
return users
},
)
LIMIT ограничивает количество записей, возвращаемых запросом, а OFFSET определяет, с какой записи начинать выборку. Таким образом, для получения первой страницы используется offset = 0, для второй — offset = 1, и так далее.
Параметр page позволяет клиенту указать, какую страницу данных он хочет получить. Например, запрос с page=2 вернет вторую страницу данных. Теперь, если вы выполните запрос с параметром page, вы получите соответствующую страницу пользователей:
curl "localhost:3000/api/users?page=2" | npx node-jq
[
{
"id": 2,
"fullName": "Lance Beer",
"email": "Lew_Hoeger@hotmail.com",
"updatedAt": null,
"createdAt": "1724980072"
}
]
Один пользователь
Следующим шагом добавим возможность получения данных конкретного пользователя по его идентификатору (id). Эндпоинт будет располагаться по адресу /api/users/:id.
fastify.get(
'/users/:id',
async (request) => {
const user = await db.query.users.findFirst({
where: eq(schemas.users.id, request.params.id),
})
fastify.assert(user, 404)
return user
},
)
Этот эндпоинт принимает параметр id из URL и использует его для поиска пользователя в базе данных. Если пользователь с таким идентификатором найден, он возвращается в виде JSON-объекта. В случае, если пользователь не найден, возвращается ошибка с кодом 404.
Последнее поведение реализовано с помощью механизма fastify.assert(). Он позволяет сократить логику проверки существования объекта и сразу вернуть правильный код ответа если объекта не существует. Первым параметром метод принимает объект, который может быть null
, вторым - код ответа если вместо объекта был передан null
.
Создание пользователя
Для создания нового пользователя добавим следующий API-эндпоинт /api/users. В отличие от предыдущих, он выполняется методом POST.
fastify.post(
'/users',
async (request, reply) => {
const [user] = await db.insert(schemas.users)
.values(request.body)
.returning()
return reply.code(201)
.send(user)
},
)
Этот эндпоинт принимает данные нового пользователя в теле запроса и сохраняет их в базе данных. После успешного создания пользователя сервер возвращает код 201 и данные нового пользователя. Из-за изменения кода ответа, нам недостаточно просто вернуть объект с данными. Здесь мы возвращаем объект reply
установив внутри него код ответа и добавив данные через метод send()
, который сам преобразует данные в JSON.
Обновление пользователя
Для обновления данных существующего пользователя добавим API-эндпоинт /api/users/:id. В данном случае :id это плейсхолдер, который будет заменен на конкретное число во время формирования ответа.
fastify.patch(
'/users/:id',
async (request) => {
const [user] = await db.update(schemas.users)
.set(request.body)
.where(eq(schemas.users.id, request.params.id))
.returning()
fastify.assert(user, 404)
return user
},
)
Этот эндпоинт позволяет обновить данные пользователя с указанным id. Запрос должен содержать новые данные в теле. Если пользователь с таким id найден и обновлен, возвращается обновленный объект пользователя. Если пользователя не существует, возвращается ошибка с кодом 404.
Удаление пользователя
Для удаления пользователя по его идентификатору добавим следующий API-эндпоинт /api/users/:id
fastify.delete(
'/users/:id',
async (request, reply) => {
const [user] = await db.delete(schemas.users)
.where(eq(schemas.users.id, request.params.id))
.returning()
fastify.assert(user, 404)
// Обязательно вызывать send(), иначе обработка зависнет
return reply.code(204).send()
},
)
Этот эндпоинт удаляет пользователя с указанным id из базы данных. Если пользователь найден и успешно удален, возвращается код 204 без тела ответа. Если пользователь не найден, возвращается ошибка с кодом 404. Такое поведение DELETE соответствует спецификации HTTP.
Заключение
С этими эндпоинтами мы реализовали полный набор операций CRUD (Create, Read, Update, Delete) для работы с ресурсом пользователя в нашем API. Эти примеры охватывают основные операции, которые вы будете использовать при разработке REST API, и закладывают основу для более сложных операций и взаимодействий. В дальнейшем эти базовые операции могут быть расширены и дополнены в зависимости от требований вашего приложения, например, добавлением авторизации, валидации данных, обработки ошибок и других аспектов, характерных для реального продакшена.
Самостоятельная работа
- Добавьте набор операций CRUD для каждой сущности: пользователь, курс, урок
- Запушьте изменения в репозиторий
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.