Claude Code voor Go API-sjablonen: definieer één helder handler-service-foutpatroon en genereer nieuwe endpoints die consistent blijven in je Go API.

Go-API's beginnen meestal netjes: een paar endpoints, één of twee mensen, en alles leeft in ieders hoofd. Daarna groeit de API, features worden onder tijdsdruk opgeleverd en sluipen er kleine verschillen in. Elk verschil voelt onschuldig, maar samen vertragen ze elke toekomstige wijziging.
Een veelvoorkomend voorbeeld: de ene handler decodeert JSON in een struct en geeft 400 terug met een nuttig bericht, een andere geeft 422 terug met een andere vorm, en een derde logt fouten in een ander formaat. Niets daarvan breekt de compilatie. Het zorgt alleen voor constante beslissingen en kleine herschrijvingen elke keer dat je iets toevoegt.
Je voelt de rommel op plekken zoals:
CreateUser, AddUser, RegisterUser) waardoor zoeken lastiger wordt.Met "scaffolding" bedoel ik hier een herhaalbaar sjabloon voor nieuw werk: waar code hoort, wat elke laag doet en hoe responses eruitzien. Het gaat minder om veel code genereren en meer om een consistente vorm vastleggen.
Tools zoals Claude kunnen helpen nieuwe endpoints snel te scaffolden, maar ze blijven alleen nuttig als je het patroon als regel behandelt. Jij definieert de regels, je reviewt elke diff en je draait tests. Het model vult de standaarddelen in; het krijgt niet het recht om je architectuur te herschrijven.
Een Go-API blijft makkelijk uitbreidbaar wanneer elk request hetzelfde pad volgt. Voordat je begint endpoints te genereren, kies één laagverdeling en houd je eraan.
De handler heeft alleen HTTP als taak: lees het request, roep de service aan en schrijf de response. Hij zou geen businessregels, SQL of "maar dit ene speciale geval" logica moeten bevatten.
De service beheert de use case: businessregels, beslissingen en orkestratie tussen repositories of externe calls. De service hoeft niets te weten over HTTP-zaken zoals statuscodes, headers of hoe fouten gerenderd worden.
Data access (repository/store) beheert persistentiedetails. Het vertaalt service-intenties naar SQL/queries/transacties. Het zou geen businessregels moeten afdwingen behalve basis data-integriteit, en het mag de API-responses niet vormen.
Een praktische checklist voor scheiding:
Kies één regel en buig die niet.
Een simpele aanpak:
Voorbeeld: de handler controleert dat email aanwezig is en op een e-mail lijkt. De service controleert dat het e-mailadres toegestaan is en nog niet in gebruik.
Beslis vroeg of services domeintypes of DTO's teruggeven.
Een schoon standaardpatroon is: handlers gebruiken request/response DTO's, services gebruiken domeintypes en de handler mappt domein naar response. Dat houdt de service stabiel, zelfs als het HTTP-contract verandert.
Als mapping zwaar voelt, houd het dan consistent: laat de service een domeintype plus een getypeerde fout teruggeven, en houd de JSON-vormgeving in de handler.
Als je wilt dat gegenereerde endpoints lijken alsof ze door dezelfde persoon zijn geschreven, leg dan foutresponses vroeg vast. Generatie werkt het beste wanneer het outputformaat niet onderhandelbaar is: één JSON-vorm, één status-code map en één regel voor wat wordt blootgesteld.
Begin met een enkele fout-envelope die elk endpoint bij falen teruggeeft. Houd het klein en voorspelbaar:
{
"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..."
}
Gebruik code voor machines (stabiel en voorspelbaar) en message voor mensen (kort en veilig). Plaats optionele gestructureerde data in details. Voor validatie is een eenvoudige details.fields map makkelijk te genereren en eenvoudig voor clients om naast inputvelden te tonen.
Schrijf daarna een status-code map op en houd je eraan. Hoe minder discussie per endpoint, hoe beter. Als je zowel 400 als 422 wilt, maak de scheidslijn expliciet:
bad_json -> 400 Bad Request (verkeerd gevormde JSON)validation_failed -> 422 Unprocessable Content (goed gevormde JSON, ongeldige velden)not_found -> 404 Not Foundconflict -> 409 Conflict (duplicate key, version mismatch)unauthorized -> 401 Unauthorizedforbidden -> 403 Forbiddeninternal -> 500 Internal Server ErrorBepaal wat je logt versus wat je teruggeeft. Een goede regel: clients krijgen een veilig bericht en een request ID; logs krijgen de volledige fout en interne context (SQL, upstream payloads, user IDs) die je nooit zou willen lekken.
Standaardiseer ten slotte request_id. Accepteer een inkomende ID-header als die aanwezig is (van een API-gateway), anders genereer je er één bij de edge (middleware). Hang hem aan de context, voeg hem toe aan logs en retourneer hem in elke foutresponse.
Als je wilt dat scaffolding consistent blijft, moet je mappenstructuur saai en herhaalbaar zijn. Generators volgen patronen die ze kunnen zien, maar ze gaan afdwalen als bestanden verspreid zijn of namen per feature veranderen.
Kies één naamgevingsconventie en buig er niet van af. Kies één woord voor elk ding en houd je eraan: handler, service, repo, request, response. Als de route POST /users is, noem bestanden en types rond users en create (niet soms register, soms addUser).
Een simpele layout die past bij de gebruikelijke lagen:
internal/
httpapi/
handlers/
users_handler.go
services/
users_service.go
data/
users_repo.go
apitypes/
users_types.go
Bepaal waar gedeelde types leven, want daar gaat het vaak mis. Een nuttige regel:
internal/apitypes (match JSON en validatiebehoeften).Als een type JSON-tags heeft en voor clients is ontworpen, behandel het als een API-type.
Houd handler-dependencies minimaal en maak die regel expliciet:
Schrijf een kort patroon-document in de repo-root (platte Markdown is prima). Voeg de folder tree, naamgevingsregels en één klein voorbeeldflow toe (handler -> service -> repo, plus in welk bestand elk stuk hoort). Dit is de exacte referentie die je in je generator plakt zodat nieuwe endpoints elke keer overeenkomen.
Voordat je tien endpoints genereert, maak één endpoint dat je vertrouwt. Dit is de gouden standaard: het bestand waar je naar kunt wijzen en zeggen: "Nieuwe code moet hierop lijken." Je kunt het from scratch schrijven of een bestaand endpoint refactoren totdat het overeenkomt.
Houd de handler dun. Eén zet die veel helpt: plaats een interface tussen de handler en de service zodat de handler afhankelijk is van een contract, niet van een concrete struct.
Voeg kleine comments toe in het referentie-endpoint, alleen waar gegenereerde code in de toekomst zou kunnen struikelen. Leg beslissingen uit (waarom 400 vs 422, waarom create 201 teruggeeft, waarom je interne fouten verbergt achter een generiek bericht). Sla comments over die alleen de code herhalen.
Als het referentie-endpoint werkt, extraheer helpers zodat elk nieuw endpoint minder kans krijgt af te dwalen. De meest herbruikbare helpers zijn meestal:
Dit is wat "dunne handler + interface" in de praktijk kan zijn:
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)
}
Leg het vast met een paar tests (zelfs een kleine tabeltest voor error mapping). Generatie werkt het beste wanneer het één schoon doel heeft om te imiteren.
Consistentie begint met wat je plakt en wat je verbiedt. Voor een nieuw endpoint geef je twee dingen:
Includeer de handler, servicemethode, request/response types en eventuele gedeelde helpers die het endpoint gebruikt. Schrijf daarna het contract in duidelijke termen:
POST /v1/widgets)Wees expliciet over wat moet overeenkomen: naamgeving, packagepaden en helperfuncties (WriteJSON, BindJSON, WriteError, je validator).
Een strakke prompt voorkomt "helpful" refactors. Bijvoorbeeld:
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.
Als je tests gebruikt, vraag ze expliciet op (en noem het testbestand). Anders kan het model ze overslaan of een testsetup verzinnen.
Doe na generatie een snelle diff-check. Als het gedeelde helpers, router-registratie of je standaard foutresponse heeft gewijzigd, wijs het output af en herhaal de "do not change" regels strikter.
De output is alleen zo consistent als de input die je geeft. De snelste manier om bijna-goede code te vermijden is één prompttemplate steeds opnieuw gebruiken, met een kleine contextsnapshot uit je repo.
Kopieer, plak en vul de plaatsaanduidingen in:
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.
Dit werkt omdat het drie dingen afdwingt: een contextblok (wat er bestaat), een constraintsblok (wat niet te doen) en concrete JSON-voorbeelden (zodat vormen niet afwijken). De laatste instructie is je veiligheidsvangnet: als het model onzeker is, moet het eerst vragen stellen voordat het code schrijft.
Stel dat je een "Create project" endpoint wilt toevoegen. Het doel is simpel: accepteer een naam, handhaaf een paar regels, sla het op en retourneer een nieuw ID. Het lastige is dezelfde handler-service-repo-split en dezelfde fout-JSON te behouden die je al gebruikt.
Een consistente flow ziet er zo uit:
Hier is het request dat de handler accepteert:
{ "name": "Roadmap", "owner_id": "u_123" }
Bij succes, return 201 Created. Het ID moet elke keer uit één bron komen. Bijvoorbeeld: laat Postgres het genereren en laat de repo het teruggeven:
{ "id": "p_456", "name": "Roadmap", "owner_id": "u_123", "created_at": "2026-01-09T12:34:56Z" }
Twee realistische faalpaden:
Als validatie faalt (ontbrekende of te korte naam), geef een veldniveau-fout terug met je standaardvorm en gekozen statuscode:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "name": "must be at least 3 characters" } } }
Als de naam uniek per owner moet zijn en de service een bestaand project vindt, return 409 Conflict:
{ "error": { "code": "PROJECT_NAME_TAKEN", "message": "Project name already exists", "details": { "name": "Roadmap" } } }
Een besluit dat het patroon schoon houdt: de handler checkt "is dit request correct gevormd?" terwijl de service bepaalt "is dit toegestaan?" Die scheiding maakt gegenereerde endpoints voorspelbaar.
De snelste manier om consistentie te verliezen is de generator te laten improviseren.
Een veel voorkomende drift is een nieuwe foutvorm. Het ene endpoint retourneert {error: "..."}, een ander {message: "..."} en een derde voegt een genest object toe. Los dit op door één fout-envelope en één status-code map op één plek te houden en vereis dat nieuwe endpoints die hergebruiken via importpad en functienaam. Als de generator een nieuw veld voorstelt, behandel dat dan als een API-wijzigingsverzoek, niet als een gemakzuchtige verbetering.
Handler-bloat is ook een drift: het begint klein: validatie, dan permissiechecks, dan DB-queries, dan branching op businessregels. Voor je het weet ziet elke handler er anders uit. Houd één regel: handlers vertalen HTTP naar getypte inputs en outputs; services beheren beslissingen; data access beheert queries.
Naamgevingsmismatches stapelen zich ook op. Als het ene endpoint CreateUserRequest gebruikt en een ander NewUserPayload, verlies je tijd met types zoeken en lijm schrijven. Kies een naamgevingsschema en verwerp nieuwe namen tenzij er een goede reden is.
Geef nooit rauwe databasefouten aan clients terug. Naast het lekken van details veroorzaakt het inconsistente berichten en statuscodes. Wrap interne fouten, log de oorzaak en geef een stabiele publieke foutcode terug.
Vermijd het toevoegen van nieuwe bibliotheken "voor het gemak". Elke extra validator, routerhelper of error-pakket wordt een extra stijl om te matchen.
Waarschuwingen die de meeste fouten voorkomen:
Als je twee endpoints niet kunt diffen en dezelfde vorm ziet (imports, flow, error handling), verscherp dan de prompt en genereer opnieuw voordat je merget.
Controleer eerst de structuur. Als de vorm klopt, zijn logische bugs makkelijker te vinden.
Structuurcontroles:
request_id-gedrag.Gedragscontroles:
Behandel je patroon als een gedeeld contract, niet als een voorkeur. Houd het "hoe we endpoints bouwen" document naast je code en onderhoud één referentie-endpoint dat de volledige aanpak van begin tot eind laat zien.
Schaal generatie in kleine batches. Genereer 2 tot 3 endpoints die verschillende randen raken (een simpele read, een create met validatie, een update met een not-found case). Stop dan en verfijn. Als reviews steeds dezelfde stijlafwijkingen vinden, update dan het basidoc en het referentie-endpoint voordat je meer genereert.
Een loop die je kunt herhalen:
Als je een strakker build-review loop wilt, kan een vibe-coding platform zoals Koder.ai helpen om snel te scaffolden en iteratief te werken in een chatgestuurde workflow, en daarna de broncode te exporteren zodra het overeenkomt met je standaard. Het gereedschap is minder belangrijk dan de regel: jouw baseline blijft bepalend.
Lock down een herhaalbare sjabloon vroeg: een consistente laagverdeling (handler → service → data access), één fout-envelope en een status-code map. Gebruik vervolgens één “reference endpoint” als het voorbeeld dat elk nieuw endpoint moet kopiëren.
Hou handlers HTTP-only:
Als je SQL, permissiechecks of business branching in een handler ziet, verplaats dat naar de service.
Zet businessregels en beslissingen in de service:
De service moet domeinresultaten en getypte fouten teruggeven—geen HTTP-statuscodes, geen JSON-vormgeving.
Isoleren van persistentiezaken:
Vermijd het encoderen van API-responseformaten of het afdwingen van businessregels in de repo behalve basis data-integriteit.
Een eenvoudige default:
Voorbeeld: de handler controleert dat email aanwezig is en op een e-mail lijkt; de service controleert dat die is toegestaan en nog niet in gebruik is.
Gebruik één standaard fout-envelope overal en houd die stabiel. Een praktisch formaat is:
code voor machines (stabiel)message voor mensen (kort en veilig)details voor gestructureerde extra's (zoals veldfouten)request_id voor tracingDit voorkomt client-side special cases en maakt gegenereerde endpoints voorspelbaar.
Schrijf een status-code map en volg die altijd. Een gebruikelijke verdeling:
Geef veilige, consistente publieke fouten terug en log de echte oorzaak intern.
code, korte message, plus request_idDit voorkomt het lekken van interne informatie en willekeurige verschillen in foutmeldingen tussen endpoints.
Maak één “golden” endpoint dat je vertrouwt en eis dat nieuwe endpoints erbij aansluiten:
BindJSON, WriteJSON, WriteError, enz.)Voeg dan een paar kleine tests toe (bijv. tabeltests voor error mapping) om het patroon vast te leggen.
Geef het model strikte context en beperkingen:
Weiger diffs die de architectuur “verbeteren” in plaats van de baseline te volgen.
400 voor malformed JSON (bad_json)422 voor validation failures (validation_failed)404 voor ontbrekende resources (not_found)409 voor conflicts (duplicates/version mismatches)500 voor onverwachte foutenHet sleutelwoord is consistentie: geen discussie per endpoint.