CRUD

Шість ендпоїнтів створюються як справжні файли, а не впроваджуються у рантаймі.

CRUD — це не рантайм-магія, а згенеровані файли, які можна читати й редагувати. CLI створює два невеликі файли маршрутів на модель; далі фреймворк обслуговує їх як будь-який інший файл маршруту. Нічого не синтезується з прапорця.

hopak generate crud post
# → Created file  app/routes/api/posts.ts
# → Created file  app/routes/api/posts/[id].ts

Згенеровані файли використовують crud-хелпери з @hopak/core:

// app/routes/api/posts.ts
import { crud } from '@hopak/core';
import post from '../../models/post';

export const GET = crud.list(post);
export const POST = crud.create(post);
// app/routes/api/posts/[id].ts
import { crud } from '@hopak/core';
import post from '../../../models/post';

export const GET = crud.read(post);
export const PUT = crud.update(post);
export const PATCH = crud.patch(post);
export const DELETE = crud.remove(post);

Це вся REST-поверхня. Шість ендпоїнтів під /api/<plural>/:

МетодШляхХелперПоведінка
GET/api/postscrud.list(post)Список із пагінацією
GET/api/posts/:idcrud.read(post)Один рядок, 404 якщо немає
POST/api/postscrud.create(post)Валідація тіла, створення, 201
PUT/api/posts/:idcrud.update(post)Повна валідація тіла
PATCH/api/posts/:idcrud.patch(post)Часткова валідація тіла
DELETE/api/posts/:idcrud.remove(post)204 при успіху, 404 якщо немає
curl -X POST http://localhost:3000/api/posts \
  -H 'content-type: application/json' \
  -d '{"title":"Hello Hopak","content":"It works!"}'
# → 201 {"id":1,"title":"Hello Hopak","content":"It works!", ...}

curl http://localhost:3000/api/posts?limit=10&offset=20
# → {"items":[...],"total":42,"limit":10,"offset":20}

limit за замовчуванням — 20, максимум — 100. Помилки валідації повертають 400 з деталями по полях; порушення UNIQUE повертають 409; поля password / secret / token вирізаються з відповідей (зокрема тих, що завантажені через include).

Кастомізація ендпоїнту

Просто відредагуйте згенерований файл. Щоб замінити POST /api/posts власною логікою, видаліть POST export із app/routes/api/posts.ts і напишіть власний обробник:

// app/routes/api/posts.ts
import { crud, defineRoute } from '@hopak/core';
import post from '../../models/post';

export const GET = crud.list(post);

export const POST = defineRoute({
  handler: async (ctx) => {
    // your custom create logic — e.g. force-prefix the title,
    // enforce auth, etc. — then call ctx.db!.model('post').create(...)
  },
});

Інші п’ять дієслів залишаються як є. Оскільки все у вихідних файлах, тут немає магії «override» — ви просто змінюєте те, що експортує файл.

Захист CRUD-verb через middleware

Кожен crud.* helper приймає опціональний другий аргумент з before, after, wrap:

import { crud } from '@hopak/core';
import { requireRole } from '@hopak/auth';
import { requireAuth } from '../../middleware/auth';
import post from '../../models/post';

export const GET = crud.list(post);
export const POST = crud.create(post, { before: [requireAuth()] });
export const DELETE = crud.remove(post, {
  before: [requireAuth(), requireRole('admin')],
});

Опції стосуються тільки цього дієслова. Повний контракт Before / After / Wrap — див. Middleware.

Відмова від CRUD для моделі

Не запускайте для неї hopak generate crud. Модель усе одно стане таблицею, ви лише не експонуєте HTTP-маршрути. Щоб додати їх пізніше, виконайте команду — або напишіть файл вручну, якщо вам потрібні URL поза /api/<plural>/.