Claude Code för Go-API-stommar: definiera ett rent handler–service–error-mönster och generera nya endpoints som förblir konsekventa i hela ditt Go-API.

Go-API:er börjar ofta rena: några endpoints, en eller två personer, och allt lever i allas huvud. Sedan växer API:et, funktioner släpps under press och små skillnader smyger in. Var och en känns ofarlig, men tillsammans försenar de varje framtida ändring.
Ett vanligt exempel: en handler avkodar JSON till en struct och returnerar 400 med ett hjälpsamt meddelande, en annan returnerar 422 med en annan form, och en tredje loggar fel i ett annat format. Inget av det bryter kompileringen — det skapar bara ständig beslutsfattning och små omskrivningar varje gång du lägger till något.
Du märker röran på platser som:
CreateUser, AddUser, RegisterUser) som gör sökningar svårare."Scaffolding" här betyder en upprepningsbar mall för nytt arbete: var kod ska ligga, vad varje lager gör och hur svar ser ut. Det handlar mindre om att generera massor av kod och mer om att låsa en konsekvent form.
Verktyg som Claude kan hjälpa dig att snabba upp scaffoldingen, men de förblir bara hjälpsamma om du behandlar mönstret som en regel. Du definierar reglerna, du granskar varje diff och du kör tester. Modellen fyller i standarddelarna; den får inte omdefiniera din arkitektur.
Ett Go-API är lätt att växa när varje request följer samma väg. Innan du börjar generera endpoints, välj en lagersplitt och håll dig till den.
Handlerns jobb är bara HTTP: läsa requesten, anropa servicen och skriva svaret. Den bör inte innehålla affärsregler, SQL eller "bara det här speciella fallet"-logik.
Servicen äger use-caset: affärsregler, beslut och orkestrering över repositories eller externa anrop. Den bör inte känna till HTTP-aspekter som statuskoder, headers eller hur fel renderas.
Dataåtkomst (repository/store) äger persistensdetaljer. Den översätter service-avsikter till SQL/queries/transaktioner. Den ska inte upprätthålla affärsregler utöver grundläggande dataintegritet och den ska inte forma API-responser.
En separat checklista som håller sig praktisk:
Välj en regel och böj den inte.
Ett enkelt tillvägagångssätt:
Exempel: handler kontrollerar att email finns och ser ut som en e-postadress. Servicen kontrollerar att e-postadressen är tillåten och inte redan används.
Bestäm tidigt om servicar returnerar domäntyper eller DTOs.
En ren default är: handlers använder request/response-DTOs, servicar använder domäntyper och handler mappar domän till respons. Det håller servicen stabil även när HTTP-kontraktet ändras.
Om mappningen känns tung, håll det konsekvent ändå: låt servicen returnera en domäntyp plus ett typat fel, och låt JSON-formningen ske i handlern.
Om du vill att genererade endpoints ska kännas skrivna av samma person, lås felresponserna tidigt. Generering fungerar bäst när utdataformatet är icke-förhandlingsbart: ett JSON-envelope, en statuskodskarta och en regel för vad som exponeras.
Börja med ett enda error-envelope som varje endpoint returnerar vid fel. Håll det litet och förutsägbart:
{
"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..."
}
Använd code för maskiner (stabilt och förutsägbart) och message för människor (kort och säker). Lägg strukturerad data i details. För validering är en enkel details.fields-karta lätt att generera och lätt för klienter att visa bredvid inputfält.
Skriv sedan ner en statuskodskarta och håll dig till den. Ju mindre debatt per endpoint, desto bättre. Om du vill använda både 400 och 422, gör uppdelningen explicit:
bad_json → 400 Bad Request (malformerad JSON)validation_failed → 422 Unprocessable Content (välformad JSON, ogiltiga fält)not_found → 404 Not Foundconflict → 409 Conflict (duplikatnyckel, versionsmismatch)unauthorized → 401 Unauthorizedforbidden → 403 Forbiddeninternal → 500 Internal Server ErrorBestäm vad du loggar kontra vad du returnerar. En bra regel: klienter får ett säkert meddelande och ett request ID; loggarna får det fulla felet och intern kontext (SQL, upstream-payloads, användar-ID:n) som du aldrig vill läcka.
Standardisera slutligen request_id. Acceptera en inkommande ID-header om den finns (från en API-gateway), annars generera en vid edge (middleware). Bifoga den i context, inkludera den i loggar och returnera den i varje error-response.
Om du vill att scaffolding ska vara konsekvent måste din mappstruktur vara tråkig och upprepningsbar. Generatorer följer mönster de kan se, men de driver iväg när filer är spridda eller namn ändras per feature.
Välj en namngivningskonvention och böj den inte. Välj ett ord för varje sak och håll dig till det: handler, service, repo, request, response. Om routen är POST /users, namnge filer och typer runt users och create (inte ibland register, ibland addUser).
En enkel layout som matchar de vanliga lagren:
internal/
httpapi/
handlers/
users_handler.go
services/
users_service.go
data/
users_repo.go
apitypes/
users_types.go
Bestäm var delade typer bor, eftersom detta ofta blir rörigt. En användbar regel:
internal/apitypes (matcha JSON och valideringsbehov).Om en typ har JSON-tags och är designad för klienter, behandla den som en API-typ.
Håll handlerberoenden minimala och gör regeln explicit:
Skriv ett kort mönsterdokument i repoets rot (vanlig Markdown räcker). Inkludera mappträdet, namngivningsregler och ett litet exempel-flöde (handler → service → repo, plus vilken fil varje del hör hemma i). Det är den exakta referensen du klistrar in i din generator så nya endpoints matchar strukturen varje gång.
Innan du genererar tio endpoints, skapa en endpoint du litar på. Detta är guldstandarden: filen du kan peka på och säga: "Ny kod måste se ut så här." Du kan skriva den från början eller refaktorera en befintlig tills den matchar.
Håll handlern tunn. Ett enkelt grepp som hjälper: lägg ett interface mellan handlern och servicen så handlern beror på ett kontrakt, inte en konkret struct.
Lägg små kommentarer i referensendpointen bara där genererad kod kan snubbla. Förklara beslut (varför 400 vs 422, varför create returnerar 201, varför du döljer interna fel bakom ett generiskt meddelande). Hoppa över kommentarer som bara upprepar koden.
När referensendpointen fungerar, extrahera hjälpfunktioner så varje ny endpoint har färre chanser att drifta. De mest återanvändbara hjälpverktygen är ofta:
Här är hur "tunn handler + interface" kan se ut i praktiken:
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)
}
Lås fast det med ett par tester (även ett litet tabelltest för felmappning). Generering fungerar bäst när den har ett rent mål att efterlikna.
Konsistens börjar med vad du klistrar in och vad du förbjuder. För en ny endpoint, ge två saker:
Inkludera handlern, servicemetoden, request/response-typer och eventuella delade helpers som endpointen använder. Skriv sedan kontraktet i klara termer:
POST /v1/widgets)Var tydlig med vad som måste matcha: namngivning, paketvägar och hjälpfunktioner (WriteJSON, BindJSON, WriteError, din validator).
Ett strikt prompt förhindrar "hjälpsamma" refaktorer. Till exempel:
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.
Om du använder tester, be om dem uttryckligen (och namnge testfilen). Annars kan modellen hoppa över dem eller hitta på en testuppsättning.
Gör en snabb diff-kontroll efter generering. Om den ändrade delade helpers, router-registrering eller ditt standardfel-format, avvisa outputen och upprepa "gör inte ändringar"-reglerna striktare.
Utdata är bara så konsekvent som indatan du ger. Det snabbaste sättet att undvika "nästan rätt"-kod är att återanvända en promptmall varje gång, med ett litet kontextutdrag från ditt repo.
Klistra in och fyll i platshållarna:
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.
Detta fungerar eftersom det tvingar tre saker: en kontextsektion (vad som finns), en begränsningssektion (vad som inte får göras) och konkreta JSON-exempel (så former inte driver iväg). Den sista instruktionen är din säkerhetsfångst: om modellen är osäker ska den säga det innan den skriver kod.
Säg att du vill lägga till en "Create project"-endpoint. Målet är enkelt: acceptera ett namn, upprätthåll några regler, lagra det och returnera ett nytt ID. Det knepiga är att behålla samma handler-service-repo-splitt och samma error JSON du redan använder.
Ett konsekvent flöde ser ut så här:
Här är requesten handlern accepterar:
{ "name": "Roadmap", "owner_id": "u_123" }
Vid framgång returnera 201 Created. ID:t bör komma från en plats varje gång. Till exempel: låt Postgres generera det och låt repo:t returnera det:
{ "id": "p_456", "name": "Roadmap", "owner_id": "u_123", "created_at": "2026-01-09T12:34:56Z" }
Två realistiska felvägar:
Om validering misslyckas (saknat eller för kort namn), returnera ett fältnivåfel med din standardform och vald statuskod:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "name": "must be at least 3 characters" } } }
Om namnet måste vara unikt per owner och servicen hittar ett befintligt projekt, returnera 409 Conflict:
{ "error": { "code": "PROJECT_NAME_TAKEN", "message": "Project name already exists", "details": { "name": "Roadmap" } } }
Ett beslut som håller mönstret rent: handlern kontrollerar "är denna request formad korrekt?" medan servicen äger "är detta tillåtet?" Den separationen gör genererade endpoints förutsägbara.
Det snabbaste sättet att tappa konsistens är att låta generatorn improvisera.
En vanlig drift är ett nytt felformat. En endpoint returnerar {error: "..."}, en annan returnerar {message: "..."} och en tredje lägger till ett inbäddat objekt. Fixa detta genom att behålla ett enda error-envelope och en enda statuskodskarta på ett ställe, och kräv att nya endpoints återanvänder dem via importväg och funktionsnamn. Om generatorn föreslår ett nytt fält, behandla det som en API-ändring, inte en bekvämlighet.
En annan drift är handler-bloat. Den börjar smått: validera, sedan kontrollera behörighet, sedan fråga DB, sedan göra branching på affärsregler. Snart ser varje handler annorlunda ut. Håll en regel: handlers översätter HTTP till typade inputs och outputs; servicar äger besluten; dataåtkomst äger queries.
Namnmismatchar adderar också. Om en endpoint använder CreateUserRequest och en annan använder NewUserPayload, slösar du tid på att jaga typer och skriva limkod. Välj ett namngivningsschema och avvisa nya namn om det inte finns stark anledning.
Returnera aldrig råa databaserfel till klienter. Förutom att det läcker detaljer skapar det inkonsekventa meddelanden och statuskoder. Wrappa interna fel, logga orsaken och returnera en stabil publik felkod.
Undvik att lägga till nya bibliotek "bara för bekvämlighet". Varje extra validator, router-helper eller error-paket blir ytterligare en stil att matcha.
Skyddsåtgärder som förhindrar de flesta brott:
Om du inte kan diffa två endpoints och se samma form (imports, flöde, felhantering), strama åt prompten och generera igen innan du mergar.
Innan du mergar något genererat, kontrollera struktur först. Om formen är rätt är logiska buggar lättare att hitta.
Strukturkontroller:
request_id-beteende.Funktionskontroller:
Behandla ditt mönster som ett delat kontrakt, inte en preferens. Ha dokumentet "hur vi bygger endpoints" nära din kod och behåll en referensendpoint som visar hela tillvägagångssättet end-to-end.
Skala upp generering i små batcher. Generera 2–3 endpoints som träffar olika kanter (en enkel read, en create med validering, en update med not-found-fall). Stanna sedan och förfina. Om granskningar fortsätter hitta samma stilavvikelse, uppdatera baseline-dokumentet och referensendpointen innan du genererar fler.
En loop du kan upprepa:
Om du vill ha en snävare build-review-loop kan en vibe-coding-plattform som Koder.ai (koder.ai) hjälpa dig att scaffolda och iterera snabbt i en chat-driven workflow, och sedan exportera källkoden när den matchar din standard. Verktyget betyder mindre än regeln: din baseline styr.
Lås fast en upprepningsbar mall tidigt: en konsekvent lagerindelning (handler → service → dataåtkomst), ett enhetligt error-envelope och en statuskodskarta. Använd sedan en enda “referensendpoint” som exempel som alla nya endpoints måste följa.
Håll handlers HTTP-centrerade:
Om du ser SQL, permission-checks eller affärslogik i en handler — flytta det till servicen.
Lägg affärsregler och beslut i servicen:
Servicen bör returnera domänresultat och typade fel—inga HTTP-statuskoder eller JSON-formatering.
Isolera persistens:
Undvik att koda API-responsformat eller affärsregler i repo:t utöver grundläggande dataintegritet.
Ett enkelt standardval:
Exempel: handler kontrollerar att email finns och ser ut som en e-post; servicen kontrollerar att den är tillåten och inte redan används.
Använd ett enda standardiserat error-envelope överallt och håll det stabilt. En praktisk form är:
code för maskiner (stabilt)message för människor (kort och säkert)details för strukturerad extra information (som fältnivåfel)request_id för spårningDetta undviker klientsides-undantag och gör genererade endpoints förutsägbara.
Skriv ner en statuskodskarta och följ den konsekvent. Ett vanligt upplägg:
Returnera säkra, konsekventa publika fel och logga den fulla orsaken internt.
code, kort message, och request_idDet förhindrar läckage av interna detaljer och undviker ojämna felmeddelanden över endpoints.
Skapa en “golden” referensendpoint du litar på och kräv att nya endpoints matchar den:
BindJSON, WriteJSON, WriteError, etc.)Lägg till några små tester (t.ex. tabelltester för felmappning) för att låsa mönstret.
Ge modellen strikt kontext och begränsningar:
Efter generering: avvisa diffar som “förbättrar” arkitekturen istället för att följa baseline.
400 för felaktig/malformerad JSON (bad_json)422 för valideringsfel (validation_failed)404 för saknade resurser (not_found)409 för konflikter (duplikat/versionmismatch)500 för oväntade felNyckeln är konsekvens: inga diskussioner per endpoint.