Rozwój API z AI
Twój resolver GET /posts ładuje autora każdego wpisu osobnym zapytaniem. Przeszedł przez review i demo, bo demo miało trzy wpisy. Na produkcji feed renderuje 50 wpisów, resolver odpala 51 zapytań, a pula połączeń do bazy płonie już o 9 rano. AI napisało dokładnie to, o co prosiłeś — po prostu nie znało twojego wzorca dostępu, bo prompt nigdy mu go nie podał.
AI jest naprawdę szybkie w pracy z API: spec-to-code, walidacja, middleware błędów, testy kontraktowe. Ale “szybko” zamienia się w “płonie na produkcji”, gdy pozwolisz mu improwizować kształt systemu. Niezawodny przepływ pracy to spec-first: przypnij kontrakt (OpenAPI, schemat GraphQL lub proto), każ AI generować względem niego, a o tym, kiedy jest gotowe, niech zdecydują twoje testy — nie demo.
Co z tego wyniesiesz
Dział zatytułowany „Co z tego wyniesiesz”- Pętlę spec-first, w której kontrakt napędza generowanie, więc implementacja nie może po cichu odpłynąć
- Gotowe do wklejenia prompty, które nazywają stack (Express + TypeScript + Zod + Vitest, Pact do kontraktów, k6 do obciążenia), zamiast zostawiać nawiasy
[placeholder] - Wariant dla Cursor / Claude Code / Codex dla spec-to-code, przebiegów kontraktowych w CI i regeneracji SDK
- Tryby awaryjne, które gryzą API generowane przez AI: rozjazd specyfikacji, resolvery N+1, brakujące kursory paginacji, kolejność middleware autoryzacji
Przepływ pracy
Dział zatytułowany „Przepływ pracy”-
Przypnij kontrakt. Najpierw wygeneruj specyfikację OpenAPI, schemat GraphQL lub plik proto i przejrzyj go jako człowiek. To artefakt, względem którego sprawdzane jest wszystko inne.
-
Generuj względem kontraktu. Wskaż agentowi plik specyfikacji i poproś o zaimplementowanie endpointów/resolverów z walidacją i obsługą błędów — nie o wymyślanie API na bieżąco.
-
Zablokuj zachowanie testami. Wygeneruj testy jednostkowe, integracyjne i kontraktowe. Spraw, by kształty odpowiedzi w testach dokładnie pasowały do handlerów, a potem uruchom je w CI.
-
Zregeneruj klientów. Ponownie uruchom generator SDK z (już autorytatywnej) specyfikacji, by konsumenci pozostali zsynchronizowani z serwerem.
Najpierw zaprojektuj kontrakt
Dział zatytułowany „Najpierw zaprojektuj kontrakt”Niezależnie od protokołu, najpierw skłoń AI do wyprodukowania kontraktu, zanim powstanie jakakolwiek implementacja. Bądź konkretny co do dojrzałości i konwencji, których oczekujesz.
AI zwraca specyfikację, którą możesz przejrzeć i wersjonować. Przycięty wycinek tego, czego się spodziewać:
paths: /tasks: get: summary: List tasks parameters: - { name: status, in: query, schema: { type: string, enum: [todo, in_progress, done] } } - { name: limit, in: query, schema: { type: integer, default: 20, maximum: 100 } } - { name: cursor, in: query, schema: { type: string } } responses: '200': description: Paginated task list content: application/json: schema: { $ref: '#/components/schemas/TaskList' }Dla GraphQL poproś o schemat z rozpisanymi typami connection i subskrypcjami; dla gRPC poproś o .proto ze strumieniowymi RPC i maskami pól. Dyscyplina jest ta sama: najpierw kontrakt, review, potem implementacja.
Generuj implementację względem specyfikacji
Dział zatytułowany „Generuj implementację względem specyfikacji”Teraz wskaż agentowi specyfikację i nazwij stack. Kształt odpowiedzi, który zwróci, musi pasować do tego, co będą asertować twoje testy — rozjazd w tym miejscu jest źródłem numer jeden problemu “przechodzi lokalnie, zwraca 500 w CI”.
Reprezentatywny wycinek tego, co produkuje agent:
import { Router } from 'express';import { z } from 'zod';import { requireAuth } from '../middleware/auth';
const listQuery = z.object({ status: z.enum(['todo', 'in_progress', 'done']).optional(), limit: z.coerce.number().int().min(1).max(100).default(20), cursor: z.string().optional(),});
const router = Router();
router.get('/tasks', requireAuth, async (req, res, next) => { const parsed = listQuery.safeParse(req.query); if (!parsed.success) { return res.status(400).json({ type: 'about:blank', title: 'Invalid query parameters', status: 400, errors: parsed.error.issues, }); } try { const { data, nextCursor } = await taskService.list({ ...parsed.data, userId: req.user.id, }); res.json({ data, nextCursor }); // shape matches the spec and the tests } catch (err) { next(err); }});Generowanie świadome schematu z serwerem MCP
Dział zatytułowany „Generowanie świadome schematu z serwerem MCP”Dla endpointów opartych na bazie danych pojedynczo największy skok jakości daje przekazanie agentowi twojego prawdziwego schematu, zamiast zmuszania go do zgadywania. Serwer MCP dla Postgresa zamienia “wygeneruj endpoint zadań” z rusztowania na ślepo w kod dokładny wobec schematu — z właściwymi nazwami kolumn, typami i indeksami.
Bez niego: AI wymyśla taskService.list(), a ty tracisz rundę na poprawianie nazw pól wobec twoich rzeczywistych tabel.
Z nim: agent czyta żywy schemat, generuje pasujące do niego zapytania i sygnalizuje brakujący indeks za twoim filtrem status. Dla zespołów TypeScript Prisma Postgres MCP jest wbudowany w Prisma CLI i zarządza też migracjami:
# Claude Code — register the Prisma Postgres MCP (schema + migrations)claude mcp add prisma -- npx prisma mcpTen sam serwer rejestruje się w Cursor (Settings -> MCP) i Codex (~/.codex/config.toml) — konfiguracja MCP jest identyczna we wszystkich trzech narzędziach. Jeśli potrzebujesz tylko lekkiego, jednozadaniowego wzbogacenia — powiedzmy lintowania specyfikacji OpenAPI, a nie trwałego połączenia z bazą — Agent Skill jest lżejszym dopasowaniem: zainstaluj jeden z skills.sh poleceniem npx skills add <owner/repo> (uniwersalne CLI z vercel-labs/skills), które działa w Claude Code, Cursor i Codex.
Autoryzacja, walidacja i obsługa błędów
Dział zatytułowany „Autoryzacja, walidacja i obsługa błędów”Wygeneruj przekrojowe middleware raz i zaznacz wyraźnie, że kolejność ma znaczenie.
Zablokuj kontrakt testami
Dział zatytułowany „Zablokuj kontrakt testami”Sensem testów jest tu zamrożenie kształtu odpowiedzi i kontraktu błędów, by późniejsza edycja AI nie mogła ich po cichu zmienić.
Użyj trybu Agent, by wygenerować zestaw integracyjny, a potem uruchom go inline. W Settings -> Cursor Settings -> Agents -> Auto-Run dodaj npx vitest do listy dozwolonych poleceń, by zestaw działał bez pytania, i obserwuj diff: odrzucaj każdą zmianę, w której asertowane ciało testu rozjeżdża się z rzeczywistą odpowiedzią handlera. Edycja wieloplikowa w Cursor to idealne narzędzie do “zregeneruj handler i jego test razem, by kształty pozostały zsynchronizowane”.
Uruchom zestaw kontraktowy bezgłowo, by pełnił też rolę bramki CI. W kroku GitHub Actions:
claude -p "Run the Pact consumer tests with 'npx vitest run tests/contract'. If any contract fails, summarize which field broke and why." --allowedTools "Read,Bash"Do pracy lokalnej hook PostToolUse (matcher Edit|Write), który po każdej edycji ponownie uruchamia npx vitest run, natychmiast podaje Claude nieprzechodzącą asercję, więc niezgodność kształtu wypływa w tej samej turze, w której została wprowadzona.
Wygeneruj i iteruj zestaw w TUI z --full-auto (ustawia piaskownicę workspace-write i zatwierdzenia on-request), by Codex uruchamiał testy bez pytania na każdym kroku:
codex --full-auto "Generate Pact contract tests for the tasks service in tests/contract, then run 'npx vitest run tests/contract' and fix any failures. Don't change the response shapes — fix the implementation."Do weryfikacji dostawcy wobec działającego serwisu automatyzacja Codex Cloud może uruchamiać weryfikację przy każdym pushu i raportować, utrzymując konsumenta i dostawcę w synchronizacji.
Wygenerowany test integracyjny musi odzwierciedlać kształt { data, nextCursor } z handlera:
describe('GET /tasks', () => { it('returns a cursor-paginated list', async () => { const token = await getAuthToken(); const res = await request(app) .get('/tasks?status=todo&limit=10') .set('Authorization', `Bearer ${token}`) .expect(200);
expect(res.body.data).toBeInstanceOf(Array); expect(['string', 'object']).toContain(typeof res.body.nextCursor); // string or null });});Dla obciążenia wygeneruj skrypt k6 z jawnymi progami, by regresja przerywała przebieg, a nie tylko wyglądała na powolną:
import http from 'k6/http';import { check } from 'k6';
export const options = { stages: [ { duration: '2m', target: 100 }, { duration: '5m', target: 100 }, { duration: '2m', target: 0 }, ], thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: ['rate<0.01'], },};
export default function () { const res = http.get(`${__ENV.BASE_URL}/tasks`, { headers: { Authorization: `Bearer ${__ENV.TOKEN}` }, }); check(res, { 'status is 200': (r) => r.status === 200 });}Wersjonowanie i deprecjacja
Dział zatytułowany „Wersjonowanie i deprecjacja”Gdy wydzielasz v2, wygeneruj middleware wersjonowania i wyemituj sygnał deprecjacji z wyraźnie przyszłą datą wyłączenia.
app.use('/api/v1', v1Routes);app.use('/api/v2', v2Routes);
const deprecateV1 = (_req, res, next) => { // RFC 9745: Deprecation to sf-date (RFC 9651) — uniksowy timestamp z prefiksem @, nie "true". res.setHeader('Deprecation', '@1780617600'); // 2026-06-05, data deprecjacji v1 res.setHeader('Sunset', 'Wed, 31 Dec 2026 23:59:59 GMT'); // RFC 8594: HTTP-date res.setHeader('Link', '<https://docs.example.com/migration-v2>; rel="deprecation"'); next();};Po każdej zmianie specyfikacji zregeneruj klientów, by konsumenci przesuwali się razem z tobą:
npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ./sdk/typescript