Claude Code untuk scaffolding API Go: tentukan pola handler‑service‑error yang bersih sekali, lalu hasilkan endpoint baru yang tetap konsisten di seluruh API Go Anda.

API Go biasanya mulai rapi: beberapa endpoint, satu atau dua orang, dan semuanya hidup di kepala orang-orang itu. Lalu API berkembang, fitur dikirim dengan tekanan waktu, dan perbedaan kecil mulai menyusup. Masing‑masing terasa tidak berbahaya, tapi bersama‑sama mereka memperlambat setiap perubahan di masa depan.
Contoh umum: satu handler mendekode JSON ke struct dan mengembalikan 400 dengan pesan yang membantu, yang lain mengembalikan 422 dengan bentuk yang berbeda, dan yang ketiga mencatat error dalam format lain. Tidak ada yang membuat kompilasi gagal. Hanya saja ini menciptakan pengambilan keputusan terus‑menerus dan penulisan ulang kecil setiap kali Anda menambahkan sesuatu.
Anda merasakan kekacauan di tempat‑tempat seperti:
CreateUser, AddUser, RegisterUser) yang membuat pencarian sulit."Scaffolding" di sini berarti template yang dapat diulang untuk pekerjaan baru: di mana kode diletakkan, apa yang dilakukan tiap lapisan, dan bagaimana respon terlihat. Ini lebih tentang mengunci bentuk konsisten daripada menghasilkan banyak kode.
Alat seperti Claude bisa membantu Anda menscaffold endpoint baru dengan cepat, tetapi mereka hanya tetap berguna bila Anda menganggap pola itu sebagai aturan. Anda menentukannya, Anda meninjau setiap diff, dan Anda menjalankan tes. Model mengisi bagian standar; model tidak berhak mendefinisikan ulang arsitektur Anda.
API Go tetap mudah dikembangkan ketika setiap permintaan mengikuti jalur yang sama. Sebelum Anda mulai menghasilkan endpoint, pilih satu pemisahan lapisan dan patuhi itu.
Tugas handler hanya HTTP: baca request, panggil service, dan tulis respon. Handler tidak boleh berisi aturan bisnis, SQL, atau logika "hanya untuk kasus ini".
Service menguasai use case: aturan bisnis, keputusan, dan orkestrasi antar repository atau panggilan eksternal. Service tidak perlu tahu tentang kekhawatiran HTTP seperti status code, header, atau bagaimana error dirender.
Akses data (repository/store) menguasai detail persistensi. Ia menerjemahkan intent service ke SQL/query/transaksi. Repository tidak seharusnya menegakkan aturan bisnis di luar integritas data dasar, dan tidak boleh membentuk respon API.
Checklist pemisahan yang praktis:
Pilih satu aturan dan jangan melanggarnya.
Pendekatan sederhana:
Contoh: handler memeriksa bahwa email ada dan terlihat seperti email. Service memeriksa bahwa email tersebut diizinkan dan belum digunakan.
Putuskan lebih awal apakah service mengembalikan tipe domain atau DTO.
Default yang bersih: handler menggunakan DTO request/response, service menggunakan tipe domain, dan handler memetakan domain ke response. Itu menjaga service stabil meskipun kontrak HTTP berubah.
Jika mapping terasa berat, tetap konsisten: biarkan service mengembalikan tipe domain plus error bertipe, dan pertahankan pembentukan JSON di handler.
Jika Anda ingin endpoint yang dihasilkan terasa seolah ditulis oleh orang yang sama, kunci format error sejak awal. Generasi bekerja paling baik ketika format output tidak bisa dinegosiasikan: satu bentuk JSON, satu peta status code, dan satu aturan untuk apa yang boleh dipaparkan.
Mulai dengan satu envelope error yang dikembalikan setiap endpoint saat gagal. Jaga kecil dan dapat diprediksi:
{
"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..."
}
Gunakan code untuk mesin (stabil dan dapat diprediksi) dan message untuk manusia (pendek dan aman). Letakkan data terstruktur opsional di details. Untuk validasi, peta details.fields sederhana mudah dihasilkan dan mudah bagi klien untuk menampilkan di samping input.
Selanjutnya, tuliskan peta status code dan patuhi itu. Semakin sedikit perdebatan per endpoint, semakin baik. Jika Anda ingin 400 dan 422, buat pembagian itu eksplisit:
bad_json -> 400 Bad Request (JSON malformed)validation_failed -> 422 Unprocessable Content (JSON terformat baik, field tidak valid)not_found -> 404 Not Foundconflict -> 409 Conflict (duplicate key, version mismatch)unauthorized -> 401 Unauthorizedforbidden -> 403 Forbiddeninternal -> 500 Internal Server ErrorPutuskan apa yang Anda log vs apa yang Anda kembalikan. Aturan yang baik: klien mendapat pesan yang aman dan request ID; log mendapat error penuh dan konteks internal (SQL, payload upstream, user ID) yang tidak pernah ingin Anda bocorkan.
Terakhir, standardisasi request_id. Terima header ID masuk jika ada (dari API gateway), jika tidak buat satu di edge (middleware). Lampirkan ke context, sertakan di log, dan kembalikan di setiap respon error.
Jika Anda ingin scaffolding tetap konsisten, layout folder harus membosankan dan bisa diulang. Generator mengikuti pola yang bisa dilihatnya, tapi mereka menyimpang ketika file berserakan atau nama berubah per fitur.
Pilih satu konvensi penamaan dan jangan melanggarnya. Pilih satu kata untuk tiap hal dan pertahankan: handler, service, repo, request, response. Jika rutenya POST /users, beri nama file dan tipe seputar users dan create (bukan kadang register, kadang addUser).
Layout sederhana yang cocok dengan lapisan biasa:
internal/
httpapi/
handlers/
users_handler.go
services/
users_service.go
data/
users_repo.go
apitypes/
users_types.go
Tentukan di mana tipe bersama berada, karena di sinilah proyek sering menjadi berantakan. Satu aturan berguna:
internal/apitypes (cocok dengan JSON dan kebutuhan validasi).Jika sebuah tipe memiliki tag JSON dan dirancang untuk klien, perlakukan sebagai tipe API.
Jaga dependensi handler minimal dan buat aturan itu eksplisit:
Tulis dokumen pola singkat di root repo (Markdown biasa cukup). Sertakan pohon folder, aturan penamaan, dan satu contoh alur kecil (handler -> service -> repo, plus file mana tiap potong ditempatkan). Ini referensi persis yang Anda tempel ke generator sehingga endpoint baru cocok tiap kali.
Sebelum menghasilkan sepuluh endpoint, buat satu endpoint yang Anda percayai. Ini adalah standar emas: file yang dapat Anda tunjuk dan katakan, "Kode baru harus terlihat seperti ini." Anda bisa menulis dari awal atau merombak yang ada sampai cocok.
Pertahankan handler sekecil mungkin. Satu langkah yang membantu: letakkan interface antara handler dan service sehingga handler bergantung pada kontrak, bukan struct konkret.
Tambahkan komentar kecil di endpoint referensi hanya di tempat kode yang dihasilkan di masa depan mungkin tersandung. Jelaskan keputusan (mengapa 400 vs 422, mengapa create mengembalikan 201, mengapa Anda menyembunyikan error internal di balik pesan generik). Lewati komentar yang hanya mengulang kode.
Setelah endpoint referensi bekerja, ekstrak helper sehingga setiap endpoint baru punya lebih sedikit peluang menyimpang. Helper yang paling dapat digunakan ulang biasanya:
Berikut bagaimana "handler tipis + interface" bisa terlihat dalam praktik:
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)
}
Kunci pola ini dengan beberapa tes (bahkan table test kecil untuk mapping error). Generasi bekerja paling baik saat memiliki satu target bersih untuk ditiru.
Konsistensi dimulai dari apa yang Anda tempelkan dan apa yang Anda larang. Untuk endpoint baru, berikan dua hal:
Sertakan handler, method service, tipe request/response, dan helper bersama yang digunakan endpoint. Lalu nyatakan kontrak secara jelas:
POST /v1/widgets)Jelaskan secara eksplisit apa yang harus cocok: penamaan, path package, dan fungsi helper (WriteJSON, BindJSON, WriteError, validator Anda).
Prompt ketat mencegah "refactor yang "berguna"." Contoh:
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.
Jika Anda menggunakan tes, minta tes secara eksplisit (dan beri nama file test). Kalau tidak, model mungkin melewatkannya atau mengada‑ada setup test.
Lakukan pemeriksaan diff cepat setelah generasi. Jika itu memodifikasi helper bersama, pendaftaran router, atau format error standar Anda, tolak output dan nyatakan ulang aturan "do not change" lebih tegas.
Output hanya sekonsisten masukan Anda. Cara tercepat menghindari kode "hampir benar" adalah menggunakan satu template prompt setiap kali, dengan snapshot konteks kecil yang diambil dari repo Anda.
Salin, tempel, dan isi placeholder:
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.
Ini bekerja karena memaksa tiga hal: blok konteks (apa yang ada), blok constraints (apa yang tidak boleh dilakukan), dan contoh JSON konkrit (supaya bentuk tidak menyimpang). Instruksi akhir adalah pengaman: kalau model tidak yakin, ia harus bertanya sebelum membuat perubahan pola.
Misal Anda ingin menambahkan endpoint "Create project". Tujuannya sederhana: terima nama, terapkan beberapa aturan, simpan, dan kembalikan ID baru. Bagian sulit adalah mempertahankan pemisahan handler‑service‑repo dan JSON error yang sudah Anda gunakan.
Alur konsisten terlihat seperti ini:
Berikut request yang diterima handler:
{ "name": "Roadmap", "owner_id": "u_123" }
Saat sukses, kembalikan 201 Created. ID harus berasal dari satu sumber setiap kali. Misalnya, biarkan Postgres menghasilkan dan repo mengembalikannya:
{ "id": "p_456", "name": "Roadmap", "owner_id": "u_123", "created_at": "2026-01-09T12:34:56Z" }
Dua jalur kegagalan realistis:
Jika validasi gagal (nama hilang atau terlalu pendek), kembalikan error per field menggunakan bentuk standar dan status code yang dipilih:
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "name": "must be at least 3 characters" } } }
Jika nama harus unik per owner dan service menemukan project yang ada, kembalikan 409 Conflict:
{ "error": { "code": "PROJECT_NAME_TAKEN", "message": "Project name already exists", "details": { "name": "Roadmap" } } }
Satu keputusan yang menjaga pola bersih: handler memeriksa "apakah request ini berbentuk benar?" sementara service menguasai "apakah ini diizinkan?" Pemisahan itu membuat endpoint yang dihasilkan dapat diprediksi.
Cara tercepat kehilangan konsistensi adalah membiarkan generator berimprovisasi.
Salah satu penyimpangan umum adalah bentuk error baru. Satu endpoint mengembalikan {error: "..."}, yang lain {message: "..."}, dan ketiga menambahkan objek nested. Perbaiki ini dengan menyimpan satu envelope error dan satu peta status code di satu tempat, lalu mensyaratkan endpoint baru menggunakan ulangnya lewat import path dan nama fungsi. Jika generator mengusulkan field baru, anggap itu permintaan perubahan API, bukan kenyamanan.
Penyimpangan lain adalah handler yang bengkak. Mulai kecil: validasi, lalu cek izin, lalu query DB, lalu branching aturan bisnis. Segera setiap handler terlihat berbeda. Pertahankan satu aturan: handler menerjemahkan HTTP ke input/output bertipe; service menguasai keputusan; akses data menguasai query.
Ketidaksesuaian penamaan juga menambah beban. Jika satu endpoint menggunakan CreateUserRequest dan lain NewUserPayload, Anda akan menghabiskan waktu mencari tipe dan menulis glue. Pilih skema penamaan dan tolak nama baru kecuali ada alasan kuat.
Jangan pernah mengembalikan error database mentah ke klien. Selain membocorkan detail, itu menciptakan pesan dan status code yang tidak konsisten. Bungkus error internal, log penyebabnya, dan kembalikan kode error publik yang stabil.
Hindari menambahkan library baru "hanya untuk kenyamanan". Setiap validator, helper router, atau paket error tambahan menjadi gaya lain yang harus dicocokkan.
Guardrail yang mencegah sebagian besar kerusakan:
Jika Anda tidak bisa membedakan dua endpoint dan melihat bentuk yang sama (imports, alur, penanganan error), ketatkan prompt dan regenerasi sebelum merge.
Sebelum Anda merge apa pun yang dihasilkan, periksa struktur dulu. Jika bentuknya benar, bug logika lebih mudah ditemukan.
Pemeriksaan struktur:
request_id sama.Pemeriksaan perilaku:
Anggap pola Anda sebagai kontrak bersama, bukan preferensi. Simpan dokumen "cara kita membangun endpoint" dekat dengan kode, dan pertahankan satu endpoint referensi yang menunjukkan pendekatan penuh."
Skala generasi dengan batch kecil. Hasilkan 2–3 endpoint yang menyentuh tepi berbeda (read sederhana, create dengan validasi, update dengan kasus not‑found). Lalu berhenti dan perbaiki. Jika review terus menemukan penyimpangan gaya yang sama, perbarui dokumen baseline dan endpoint referensi sebelum menghasilkan lebih banyak.
Siklus yang bisa Anda ulangi:
Jika Anda ingin loop build‑review yang lebih ketat, platform vibe‑coding seperti Koder.ai (koder.ai) dapat membantu Anda menscaffold dan iterasi cepat dalam workflow chat‑driven, lalu ekspor kode sumber setelah cocok dengan standar Anda. Alat kurang penting dibanding aturan: baseline tetap memegang kendali.
Lock down a repeatable template early: a consistent layer split (handler → service → data access), one error envelope, and a status-code map. Then use a single “reference endpoint” as the example every new endpoint must copy.
Keep handlers HTTP-only:
If you see SQL, permission checks, or business branching in a handler, push that into the service.
Put business rules and decisions in the service:
The service should return domain results and typed errors—no HTTP status codes, no JSON shaping.
Keep persistence concerns isolated:
Avoid encoding API response formats or enforcing business rules in the repo beyond basic data integrity.
A simple default:
Example: handler checks email is present and looks like an email; service checks it’s allowed and not already in use.
Use one standard error envelope everywhere and keep it stable. A practical shape is:
code for machines (stable)message for humans (short and safe)details for structured extras (like field errors)request_id for tracingThis avoids client-side special cases and keeps generated endpoints predictable.
Write down a status-code map and follow it every time. A common split:
Return safe, consistent public errors, and log the real cause internally.
code, short message, plus request_idThis prevents leaking internals and avoids random error message differences across endpoints.
Create one “golden” endpoint you trust and require new endpoints to match it:
BindJSON, WriteJSON, WriteError, etc.)Then add a couple of small tests (even table tests for error mapping) to lock the pattern in.
Give the model strict context and constraints:
After generation, reject diffs that “improve” architecture instead of following the baseline.
400 for malformed JSON (bad_json)422 for validation failures (validation_failed)404 for missing resources (not_found)409 for conflicts (duplicates/version mismatches)500 for unexpected failuresThe key is consistency: no debating per endpoint.