CRUD API для статей
CRUD — четыре базовых действия с данными: создать (Create), прочитать (Read), изменить (Update), удалить (Delete). Здесь все они собираются в один файл маршрутов статей, который работает с базой из прошлого урока и проверяет данные схемами.
Маршруты API
| Метод и адрес | Действие | Код успеха |
|---|---|---|
GET /posts | Список статей | 200 |
GET /posts/:id | Одна статья | 200 |
POST /posts | Создать статью | 201 |
PUT /posts/:id | Изменить статью | 200 |
DELETE /posts/:id | Удалить статью | 204 |
Схемы
Схемы вынесены в начало файла src/routes/posts.js, чтобы маршруты оставались короткими:
// src/routes/posts.js
const params = {
type: 'object',
properties: { id: { type: 'integer' } },
};
const postInput = {
type: 'object',
required: ['title', 'body'],
properties: {
title: { type: 'string', minLength: 3, maxLength: 120 },
body: { type: 'string', minLength: 1 },
},
};params переводит id в число, postInput проверяет тело при создании и изменении.
Маршруты
Ниже в том же файле:
// src/routes/posts.js
export default async function postRoutes(app) {
const findPost = (id) => {
const post = app.db.prepare('SELECT * FROM posts WHERE id = ?').get(id);
if (!post) {
throw app.httpErrors.notFound('Статья не найдена');
}
return post;
};
app.get('/posts', async () => {
return app.db.prepare('SELECT id, title, created_at FROM posts ORDER BY id DESC').all();
});
app.get('/posts/:id', { schema: { params } }, async (request) => {
return findPost(request.params.id);
});
app.post('/posts', { schema: { body: postInput } }, async (request, reply) => {
const { title, body } = request.body;
const result = app.db
.prepare('INSERT INTO posts (title, body) VALUES (?, ?)')
.run(title, body);
return reply.code(201).send(findPost(result.lastInsertRowid));
});
app.put('/posts/:id', { schema: { params, body: postInput } }, async (request) => {
const { id } = request.params;
findPost(id);
const { title, body } = request.body;
app.db.prepare('UPDATE posts SET title = ?, body = ? WHERE id = ?').run(title, body, id);
return findPost(id);
});
app.delete('/posts/:id', { schema: { params } }, async (request, reply) => {
const result = app.db.prepare('DELETE FROM posts WHERE id = ?').run(request.params.id);
if (result.changes === 0) {
throw app.httpErrors.notFound('Статья не найдена');
}
return reply.code(204).send();
});
}Функция findPost используется в четырёх маршрутах: она либо возвращает статью, либо бросает ошибку 404.
Проверка
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title":"Привет, Fastify","body":"Первая статья в базе"}'
curl http://localhost:3000/posts
curl -X DELETE -i http://localhost:3000/posts/1Первая команда вернёт статью с id и created_at, вторая — список, третья — ответ 204 No Content. После перезапуска сервера статьи остаются на месте.
Комментарии
Маршруты комментариев в src/routes/comments.js строятся так же. Например, добавление комментария к статье:
// src/routes/comments.js
app.post('/posts/:id/comments', async (request, reply) => {
const { author, text } = request.body;
const result = app.db
.prepare('INSERT INTO comments (post_id, author, text) VALUES (?, ?, ?)')
.run(request.params.id, author, text);
return reply.code(201).send({ id: result.lastInsertRowid, author, text });
});Добавьте к нему схемы params и body по образцу статей.
Частая ошибка
Маршрут PUT без проверки существования статьи «успешно» обновит ноль строк и вернёт пустой ответ с кодом 200. Сначала ищите запись и бросайте 404, если её нет, — как это делает findPost.