Encodera

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

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

Оглавление

CRUD API статей

CRUD — четыре базовых действия с данными: создать (Create), прочитать (Read), изменить (Update), удалить (Delete). Соберём их для статей блога в одном файле, используя всё из прошлых уроков: группу маршрутов, zod, HTTPException и базу SQLite.

Маршруты

ЗапросДействиеКод ответа
GET /postsсписок статей200
GET /posts/:idодна статья200 или 404
POST /postsсоздание201
PUT /posts/:idизменение200 или 404
DELETE /posts/:idудаление204 или 404

Файл маршрутов

Замените содержимое src/routes/posts.ts:

// src/routes/posts.ts
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { db, type Post } from '../db.js'
import { postSchema } from '../schemas.js'
 
const idParam = z.object({ id: z.coerce.number().int().positive() })
 
function findPost(id: number): Post {
  const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(id) as Post | undefined
  if (!post) {
    throw new HTTPException(404, { message: 'Статья не найдена' })
  }
  return post
}
 
const posts = new Hono()
 
posts.get('/', (c) => {
  const rows = db.prepare('SELECT * FROM posts ORDER BY id DESC').all() as Post[]
  return c.json(rows)
})
 
posts.get('/:id', zValidator('param', idParam), (c) => {
  const { id } = c.req.valid('param')
  return c.json(findPost(id))
})
 
posts.post('/', zValidator('json', postSchema), (c) => {
  const { title, body } = c.req.valid('json')
  const result = db
    .prepare('INSERT INTO posts (title, body) VALUES (?, ?)')
    .run(title, body)
  return c.json(findPost(Number(result.lastInsertRowid)), 201)
})
 
posts.put('/:id', zValidator('param', idParam), zValidator('json', postSchema), (c) => {
  const { id } = c.req.valid('param')
  const { title, body } = c.req.valid('json')
  findPost(id)
  db.prepare('UPDATE posts SET title = ?, body = ? WHERE id = ?').run(title, body, id)
  return c.json(findPost(id))
})
 
posts.delete('/:id', zValidator('param', idParam), (c) => {
  const { id } = c.req.valid('param')
  findPost(id)
  db.prepare('DELETE FROM posts WHERE id = ?').run(id)
  return c.body(null, 204)
})
 
export default posts

Функция findPost() ищет статью и выбрасывает HTTPException, если её нет. Благодаря ей проверка на 404 написана один раз, а app.onError() из урока об ошибках превратит исключение в JSON. В маршрут put подключены два валидатора: для номера в адресе и для тела.

Проверка

Создайте статью:

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "Hono и SQLite", "body": "Статья из базы"}'

В ответ придёт статья с номером, датой и кодом 201. Дальше:

curl http://localhost:3000/posts/1
curl -X PUT http://localhost:3000/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Новый заголовок", "body": "Новый текст"}'
curl -i -X DELETE http://localhost:3000/posts/1

Первая команда вернёт статью, вторая — её обновлённую версию, третья — пустой ответ с кодом 204. После удаления GET /posts/1 вернёт 404. Данные сохраняются и после перезапуска сервера.

Комментарии

Маршруты комментариев в src/routes/comments.ts строятся так же: GET /posts/:postId/comments выбирает строки WHERE post_id = ?, а POST вставляет комментарий с номером статьи из адреса. Перед вставкой проверьте, что статья существует, — иначе получится комментарий к несуществующей статье.

Частая ошибка

Ответ 204 отправляют через c.json(null, 204) или с телом-сообщением. Код 204 означает «нет содержимого», и тело у такого ответа должно быть пустым. Используйте c.body(null, 204), а если хотите вернуть сообщение — выберите код 200.

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

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