REST API для статей
API — адреса сервера, которые отвечают не HTML-страницами, а данными в JSON. Ими пользуются мобильные приложения, фронтенд на React или Vue и другие сервисы. REST — договорённость, как устроить такие адреса: у набора записей один адрес, а действие определяется HTTP-методом.
Соглашения
| Метод и адрес | Действие | Код ответа |
|---|---|---|
GET /api/posts | Список статей | 200 |
GET /api/posts/:id | Одна статья | 200 или 404 |
POST /api/posts | Создать статью | 201 или 422 |
PATCH /api/posts/:id | Изменить статью | 200 или 404 |
DELETE /api/posts/:id | Удалить статью | 204 или 404 |
Модель
Добавьте в src/models/post.js из урока о базе данных две функции:
// src/models/post.js
export function update(id, { title, body }) {
db.prepare('UPDATE posts SET title = ?, body = ? WHERE id = ?').run(title, body, id);
}
export function remove(id) {
return db.prepare('DELETE FROM posts WHERE id = ?').run(id).changes > 0;
}changes — сколько строк затронул запрос. Если ноль, статьи с таким номером не было.
Контроллер
API держат отдельно от страниц. Создайте src/controllers/api/postsController.js:
// src/controllers/api/postsController.js
import * as Post from '../../models/post.js';
import { postSchema } from '../../validation/postSchema.js';
export function index(req, res) {
res.json(Post.findAll());
}
export function show(req, res) {
const post = Post.findById(Number(req.params.id));
if (!post) return res.status(404).json({ error: 'Статья не найдена' });
res.json(post);
}
export function store(req, res) {
const result = postSchema.safeParse(req.body ?? {});
if (!result.success) {
return res.status(422).json({ errors: result.error.issues.map((issue) => issue.message) });
}
const id = Post.create(result.data);
res.status(201).json(Post.findById(id));
}
export function update(req, res) {
const post = Post.findById(Number(req.params.id));
if (!post) return res.status(404).json({ error: 'Статья не найдена' });
const result = postSchema.partial().safeParse(req.body ?? {});
if (!result.success) {
return res.status(422).json({ errors: result.error.issues.map((issue) => issue.message) });
}
Post.update(post.id, { ...post, ...result.data });
res.json(Post.findById(post.id));
}
export function destroy(req, res) {
if (!Post.remove(Number(req.params.id))) {
return res.status(404).json({ error: 'Статья не найдена' });
}
res.sendStatus(204);
}Схема postSchema — та же, что проверяет форму. partial() делает все поля необязательными: при изменении можно прислать только заголовок.
Маршруты
// src/routes/api/posts.js
import { Router } from 'express';
import * as api from '../../controllers/api/postsController.js';
const router = Router();
router.get('/', api.index);
router.post('/', api.store);
router.get('/:id', api.show);
router.patch('/:id', api.update);
router.delete('/:id', api.destroy);
export default router;В src/app.js подключите его после express.json(): app.use('/api/posts', apiPostsRouter).
Проверка
curl http://localhost:3000/api/posts
curl -X POST http://localhost:3000/api/posts -H "Content-Type: application/json" -d '{"title":"Статья из API","body":"Текст"}'
curl -X PATCH http://localhost:3000/api/posts/1 -H "Content-Type: application/json" -d '{"title":"Новый заголовок"}'
curl -X DELETE http://localhost:3000/api/posts/1Первая команда вернёт массив статей, вторая — новую статью с номером, третья — изменённую, четвёртая — пустой ответ с кодом 204.
Частая ошибка
Ошибки API тоже должны быть в JSON. Если обработчик 404 и обработчик ошибок отдают HTML-страницу, программа-клиент не сможет её разобрать. Для адресов /api подключите отдельные обработчики, которые отвечают через res.json(), — например, app.use('/api', (req, res) => res.status(404).json({ error: 'Не найдено' })) перед общей страницей 404.