Claude Code do tworzenia szkieletu API w Go: zdefiniuj czysty wzorzec handler-service-error raz, a potem generuj nowe endpointy, które pozostaną spójne w całym API Go.

API w Go zwykle zaczynają czysto: kilka endpointów, jedna lub dwie osoby, a wszystko żyje w głowach. Potem API rośnie, funkcje są wypuszczane pod presją i wkradają się małe różnice. Każda z nich wydaje się niegroźna, ale razem spowalniają każdą przyszłą zmianę.
Typowy przykład: jeden handler dekoduje JSON do struktury i zwraca 400 z pomocnym komunikatem, inny zwraca 422 o innym kształcie, a trzeci loguje błędy w innym formacie. Nic z tego nie łamie kompilacji. Po prostu powoduje ciągłe podejmowanie decyzji i drobne przeróbki za każdym razem, gdy dodajesz coś nowego.
Bałagan odczujesz w miejscach takich jak:
CreateUser, AddUser, RegisterUser), który utrudnia wyszukiwanie.„Scaffolding” tutaj oznacza powtarzalny szablon dla nowej pracy: gdzie umieszczać kod, co robi każda warstwa i jak wyglądają odpowiedzi. Chodzi mniej o generowanie dużej ilości kodu, a bardziej o zamknięcie spójnego kształtu.
Narzędzia jak Claude mogą pomóc szybko zaszkieletować nowe endpointy, ale pozostają użyteczne tylko wtedy, gdy traktujesz wzorzec jak regułę. Ty definiujesz reguły, przeglądasz każdy diff i uruchamiasz testy. Model wypełnia standardowe części; nie może redefiniować twojej architektury.
API w Go łatwo się rozwija, gdy każde żądanie podąża tą samą ścieżką. Zanim zaczniesz generować endpointy, wybierz jeden podział warstw i trzymaj się go.
Zadaniem handlera jest tylko HTTP: odczytać żądanie, wywołać service i zapisać odpowiedź. Nie powinien zawierać reguł biznesowych, SQL ani „tylko tego jednego specjalnego przypadku”.
Service odpowiada za przypadek użycia: reguły biznesowe, decyzje i orkiestrację między repozytoriami lub wywołaniami zewnętrznymi. Nie powinien znać spraw HTTP, jak kody statusu, nagłówki czy sposób renderowania błędów.
Dostęp do danych (repository/store) odpowiada za szczegóły trwałości. Tłumaczy intencje service na SQL/zapytania/transakcje. Nie powinien wymuszać reguł biznesowych poza podstawową integralnością danych ani kształtować odpowiedzi API.
Lista kontrolna separacji, praktyczna:
Wybierz jedną regułę i jej nie naginaj.
Proste podejście:
Przykład: handler sprawdza, że email jest obecny i wygląda jak adres e-mail. Service sprawdza, że email jest dozwolony i nie jest już używany.
Zdecyduj wcześnie, czy serwisy zwracają typy domenowe czy DTO.
Czysty domyśl to: handlery używają request/response DTO, serwisy używają typów domenowych, a handler mapuje domenę na odpowiedź. To utrzymuje service stabilny, nawet gdy kontrakt HTTP się zmienia.
Jeśli mapowanie wydaje się uciążliwe, trzymaj się spójnie: niech service zwraca typ domenowy plus typowany błąd, a kształt JSON zostaw w handlerze.
Jeśli chcesz, by generowane endpointy wyglądały jak napisane przez tę samą osobę, zamknij odpowiedzi błędów wcześnie. Generowanie działa najlepiej, gdy format wyjścia jest niepodważalny: jedna struktura JSON, jedna mapa kodów statusu i jedna zasada, co jest ujawniane.
Zacznij od pojedynczej koperty błędu, której każdy endpoint używa przy porażce. Trzymaj ją małą i przewidywalną:
{
"code": "validation_failed",
"message": "One or more fields are invalid.",
"details": {
"fields": {
"email": "must be a valid email address",
"age": "must be greater than 0"
}
},
"request_id": "req_01HR..."
}
Użyj code dla maszyn (stabilne i przewidywalne) i message dla ludzi (krótki i bezpieczny). Włóż opcjonalne dane strukturalne do details. Dla walidacji prosty mapowany obiekt details.fields jest łatwy do wygenerowania i prosty do wyświetlenia przez klientów obok pól formularza.
Następnie spisz mapę kodów statusu i trzymaj się jej. Im mniej sporów na temat każdego endpointu, tym lepiej. Jeśli chcesz zarówno 400 jak i 422, rozgranicz to explicite:
bad_json -> 400 Bad Request (malformed JSON)validation_failed -> 422 Unprocessable Content (well-formed JSON, invalid fields)not_found -> 404 Not Foundconflict -> 409 Conflict (duplicate key, version mismatch)unauthorized -> 401 Unauthorizedforbidden -> 403 Forbiddeninternal -> 500 Internal Server ErrorZdecyduj, co logujesz, a co zwracasz. Dobra zasada: klient otrzymuje bezpieczny komunikat i request ID; logi zawierają pełny błąd i kontekst wewnętrzny (SQL, payloady upstream, identyfikatory użytkowników), których nigdy nie chcesz ujawniać.
Na koniec ustandaryzuj request_id. Akceptuj nagłówek z ID, jeśli jest obecny (z API gateway), w przeciwnym razie generuj je na krawędzi (middleware). Dołączaj do kontekstu, umieszczaj w logach i zwracaj w każdej odpowiedzi błędu.
Jeśli chcesz, żeby scaffolding pozostał spójny, układ folderów musi być nudny i powtarzalny. Generatory podążają za wzorcami, które widzą, ale dryfują, gdy pliki są rozsiane lub nazwy zmieniają się w zależności od funkcji.
Wybierz jedno konwencję nazewniczą i jej się trzymaj. Wybierz jedno słowo dla każdej rzeczy i trzymaj je: handler, service, repo, request, response. Jeśli trasa to POST /users, nazwy plików i typów trzymaj wokół users i create (nie czasem register, czasem addUser).
Prosty układ pasujący do typowych warstw:
internal/
httpapi/
handlers/
users_handler.go
services/
users_service.go
data/
users_repo.go
apitypes/
users_types.go
Zdecyduj, gdzie trzymać typy współdzielone, bo tutaj projekty często się komplikują. Jedna użyteczna zasada:
internal/apitypes (dopasowane do JSON i wymagań walidacyjnych).Jeśli typ ma tagi JSON i jest projektowany pod klientów, traktuj go jako typ API.
Trzymaj zależności handlera minimalne i zrób tę zasadę explicite:
Napisz krótki dokument wzorca w repo root (zwykły Markdown wystarczy). Dołącz strukturę folderów, zasady nazewnictwa i jeden mały przykład przepływu (handler -> service -> repo oraz które pliki zawierają każdą część). To jest dokładna referencja, którą wklejasz do generatora, żeby nowe endpointy za każdym razem pasowały do struktury.
Zanim wygenerujesz dziesięć endpointów, stwórz jeden endpoint, któremu ufasz. To złoty standard: plik, na który możesz wskazać i powiedzieć: „Nowy kod musi tak wyglądać.” Możesz go napisać od zera albo zrefaktoryzować istniejący, aż będzie pasował.
Trzymaj handler cienki. Jeden ruch, który bardzo pomaga: wstaw interfejs między handlerem a service, żeby handler zależał od kontraktu, a nie od konkretnej struktury.
Dodaj krótkie komentarze w referencyjnym endpoint tylko tam, gdzie przyszły generowany kod może się potknąć. Wyjaśnij decyzje (dlaczego 400 vs 422, dlaczego create zwraca 201, dlaczego ukrywasz błędy wewnętrzne za generycznym komunikatem). Pomiń komentarze, które tylko powtarzają kod.
Gdy referencyjny endpoint działa, wyciągnij helpery tak, by każdy nowy endpoint miał mniej szans na dryf. Najbardziej wielokrotnego użytku helpery to zwykle:
Oto jak „cienki handler + interfejs” może wyglądać w praktyce:
type UserService interface {
CreateUser(ctx context.Context, in CreateUserInput) (User, error)
}
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
var in CreateUserRequest
if err := BindJSON(r, &in); err != nil {
WriteError(w, ErrBadJSON) // 400: malformed JSON
return
}
if err := Validate(in); err != nil {
WriteError(w, err) // 422: validation details
return
}
user, err := h.svc.CreateUser(r.Context(), in.ToInput())
if err != nil {
WriteError(w, err)
return
}
WriteJSON(w, http.StatusCreated, user)
}
Zamknij to paroma testami (nawet mały table test dla mapowania błędów). Generowanie działa najlepiej, gdy ma jeden czysty wzorzec, naśladowany.
Spójność zaczyna się od tego, co wklejasz i czego zabraniasz. Dla nowego endpointu daj dwie rzeczy:
Dołącz handler, metodę service, typy request/response i wszelkie wspólne helpery, których endpoint używa. Następnie określ kontrakt prostymi słowami:
POST /v1/widgets)Bądź eksplicytny co musi się zgadzać: nazwy, ścieżki pakietów i funkcje helperów (WriteJSON, BindJSON, WriteError, twój validator).
Ścisły prompt zapobiega „pomocnym” refaktom. Na przykład:
Using the reference endpoint below and the pattern notes, generate a new endpoint.
Contract:
- Route: POST /v1/widgets
- Request: {"name": string, "color": string}
- Response: {"id": string, "name": string, "color": string, "createdAt": string}
- Errors: invalid JSON -> 400; validation -> 422; duplicate name -> 409; unexpected -> 500
Output ONLY these files:
1) internal/http/handlers/widgets_create.go
2) internal/service/widgets.go (add method only)
3) internal/types/widgets.go (add types only)
Do not change: router setup, existing error format, existing helpers, or unrelated files.
Must use: package paths and helper functions exactly as in the reference.
Jeśli używasz testów, poproś o nie explicite (i nazwij plik testowy). W przeciwnym razie model może je pominąć lub wymyślić własne ustawienie testowe.
Zrób szybkie sprawdzenie diff po wygenerowaniu. Jeśli zmienił wspólne helpery, rejestrację routera lub standardową odpowiedź błędu, odrzuć output i ostrzej powtórz reguły „do not change”.
Output jest tylko tak spójny, jak wejście. Najszybszy sposób, by uniknąć „prawie dobrego” kodu, to używać tego samego szablonu promptu za każdym razem, z małym snapshotem kontekstu z repo.
Skopiuj, wklej i wypełnij pola:
You are editing an existing Go HTTP API.
CONTEXT
- Folder tree (only the relevant parts):
<paste a small tree: internal/http, internal/service, internal/repo, etc>
- Key types and patterns:
- Handler signature style: <example>
- Service interface style: <example>
- Request/response DTOs live in: <package>
- Standard error response JSON:
{
"error": {
"code": "invalid_argument",
"message": "...",
"details": {"field": "reason"}
}
}
- Status code map:
invalid_json -> 400
invalid_argument -> 422
not_found -> 404
conflict -> 409
internal -> 500
TASK
Add a new endpoint: <METHOD> <PATH>
- Handler name: <Name>
- Service method: <Name>
- Request JSON example:
{"name":"Acme"}
- Success response JSON example:
{"id":"123","name":"Acme"}
CONSTRAINTS
- No new dependencies.
- Keep functions small and single-purpose.
- Match existing naming, folder layout, and error style exactly.
- Do not refactor unrelated files.
ACCEPTANCE CHECKS
- Code builds.
- Existing tests pass (add tests only if the repo already uses them for handlers/services).
- Run gofmt on changed files.
FINAL INSTRUCTION
Before writing code, list any assumptions you must make. If an assumption is risky, ask a short question instead.
To działa, ponieważ wymusza trzy rzeczy: blok kontekstu (co istnieje), blok ograniczeń (czego nie robić) i konkretne przykłady JSON (żeby kształty się nie rozjechały). Ostatnia instrukcja to zabezpieczenie: jeśli model nie jest pewien, powinien zapytać zanim wygeneruje kod.
Powiedzmy, że chcesz dodać endpoint „Create project”. Cel jest prosty: przyjąć nazwę, wymusić kilka reguł, zapisać i zwrócić nowe ID. Trudność polega na utrzymaniu podziału handler-service-repo i tego samego JSON błędu, którego już używasz.
Spójny przepływ wygląda tak:
Oto żądanie, które handler akceptuje:
{ "name": "Roadmap", "owner_id": "u_123" }
Po sukcesie zwróć 201 Created. ID powinno pochodzić z jednego miejsca za każdym razem. Na przykład pozwól Postgresowi je wygenerować, a repo niech je zwraca:
{ "id": "p_456", "name": "Roadmap", "owner_id": "u_123", "created_at": "2026-01-09T12:34:56Z" }
Dwie realistyczne ścieżki porażki:
Jeśli walidacja zawiedzie (brak lub za krótka nazwa), zwróć błąd na poziomie pola używając standardowego kształtu i wybranego kodu statusu:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "name": "must be at least 3 characters" } } }
Jeśli nazwa musi być unikalna na właściciela i service znajdzie istniejący projekt, zwróć 409 Conflict:
{ "error": { "code": "PROJECT_NAME_TAKEN", "message": "Project name already exists", "details": { "name": "Roadmap" } } }
Jedna decyzja, która utrzymuje wzorzec czysty: handler sprawdza „czy to żądanie ma właściwy kształt?”, a service odpowiada za „czy to jest dozwolone?”. To rozdzielenie sprawia, że generowane endpointy są przewidywalne.
Najszybszy sposób na utratę spójności to pozwolić generatorowi improwizować.
Jednym z częstych dryfów jest nowy kształt błędu. Jeden endpoint zwraca {error: "..."}, inny {message: "..."}, a trzeci dodaje zagnieżdżony obiekt. Napraw to, trzymając jedną kopertę błędu i jedną mapę kodów w jednym miejscu, a następnie wymagaj, by nowe endpointy je ponownie wykorzystywały przez import ścieżki i nazwę funkcji. Jeśli generator proponuje nowe pole, traktuj to jako zmianę API, nie jako wygodę.
Inny dryf to rozrastanie handlera. Zaczyna się niewinnie: waliduj, potem sprawdź uprawnienia, potem zapytaj DB, potem rozgałęzienia reguł biznesowych. Wkrótce każdy handler wygląda inaczej. Trzymaj jedną regułę: handlery tłumaczą HTTP na typowane inputy i outputy; serwisy posiadają decyzje; dostęp do danych zajmuje się zapytaniami.
Niezgodności nazw również się sumują. Jeśli jeden endpoint używa CreateUserRequest, a inny NewUserPayload, stracisz czas na dopasowywanie typów i pisanie glue. Wybierz schemat nazewnictwa i odrzucaj nowe nazwy, chyba że jest ku temu silny powód.
Nigdy nie zwracaj surowych błędów bazy danych klientom. Poza wyciekiem szczegółów, tworzy to niespójne komunikaty i kody statusu. Owijaj błędy wewnętrzne, loguj przyczynę i zwracaj stabilny publiczny kod błędu.
Unikaj dodawania nowych bibliotek „dla wygody”. Każdy dodatkowy validator, helper routera czy pakiet błędów staje się kolejnym stylem do dopasowania.
Zabezpieczenia, które zapobiegają większości uszkodzeń:
Jeśli nie możesz porównać dwóch endpointów i zobaczyć tego samego kształtu (importy, flow, obsługa błędów), zaostrz prompt i wygeneruj ponownie przed mergem.
Zanim zmerge'ujesz cokolwiek wygenerowanego, najpierw sprawdź strukturę. Jeśli kształt jest poprawny, błędy logiczne łatwiej znaleźć.
Kontrole struktury:
request_id.Kontrole zachowania:
Traktuj wzorzec jako wspólny kontrakt, nie preferencję. Trzymaj dokument „jak budujemy endpointy” blisko kodu i utrzymuj jeden referencyjny endpoint pokazujący pełne podejście end-to-end.
Skaluj generowanie małymi partiami. Generuj 2–3 endpointy, które trafiają różne przypadki (proste read, create z walidacją, update z not-found). Potem zatrzymaj się i dopracuj. Jeśli przeglądy ciągle znajdują ten sam dryf stylu, zaktualizuj dokument bazowy i referencyjny endpoint zanim wygenerujesz więcej.
Pętla do powtarzania:
Jeśli chcesz ciaśniejszego cyklu build-review, platforma vibe-coding jak Koder.ai (koder.ai) może pomóc Ci szkielować i iterować szybko w chatowym workflow, a potem eksportować źródło, gdy pasuje do twojego standardu. Narzędzie ma mniejsze znaczenie niż reguła: twoja baza stoi na straży.
Zamroź powtarzalny szablon na wczesnym etapie: spójny podział warstw (handler → service → data access), jedna koperta błędu i mapa statusów HTTP. Następnie użyj jednego „reference endpoint”, którego każde nowe endpoint musi się trzymać.
Trzymaj handlery tylko dla HTTP:
Jeśli widzisz SQL, sprawdzanie uprawnień lub rozgałęzienia biznesowe w handlerze, przenieś to do service.
Umieść logikę biznesową i decyzje w service:
Service powinien zwracać rezultaty domenowe i typowane błędy — bez kodów HTTP i bez formowania JSON.
Zachowaj kwestie trwałości danych w izolacji:
Unikaj kodowania formatów odpowiedzi API lub wymuszania reguł biznesowych w repo, poza podstawową integralnością danych.
Proste domyślne rozłożenie:
Przykład: handler sprawdza, że email istnieje i wygląda jak email; service sprawdza, czy jest dozwolony i nie jest już używany.
Używaj jednej, standardowej koperty błędu wszędzie i trzymaj ją stabilną. Praktyczny kształt:
code dla maszyn (stabilne)message dla ludzi (krótki i bezpieczny)details dla danych strukturalnych (np. błędy pól)request_id dla śledzeniaTo zapobiega special-case'om po stronie klienta i ułatwia przewidywalność generowanych endpointów.
Spisz mapę statusów i trzymaj się jej za każdym razem. Typowy podział:
400 dla źle sformatowanego JSON (bad_json)422 dla błędów walidacji (validation_failed)404 dla nie znaleziono (not_found)Zwracaj bezpieczne, spójne błędy publiczne i loguj prawdziwą przyczynę wewnętrznie.
code, krótki message, plus request_idTo zapobiega wyciekom szczegółów i różnicom w komunikatach między endpointami.
Stwórz jeden „golden” endpoint, któremu ufasz i wymagaj, by nowe endpointy się do niego upodabniały:
BindJSON, WriteJSON, WriteError, itd.)Dodaj kilka małych testów (np. table tests dla mapowania błędów) żeby utrwalić wzorzec.
Daj modelowi ścisły kontekst i ograniczenia:
Po generacji odrzuć diffs, które „ulepszają” architekturę zamiast trzymać się baseline.
409 dla konfliktów (duplikat/wersjonowanie)500 dla nieoczekiwanych błędówKlucz to spójność: bez debaty dla każdego endpointu.