Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.