7 min

Claude Code para scaffolding de APIs en Go: manejadores y servicios consistentes

Claude Code para scaffolding de APIs en Go: define un patrón limpio handler–service–error y genera endpoints nuevos que mantengan consistencia en tu API Go.

Claude Code para scaffolding de APIs en Go: manejadores y servicios consistentes

Por qué las APIs en Go se vuelven desordenadas cuando no se fijan patrones temprano

Las APIs en Go suelen empezar limpias: unos pocos endpoints, una o dos personas, y todo vive en la cabeza de todos. Luego la API crece, las funcionalidades se entregan bajo presión y se cuelan pequeñas diferencias. Cada una parece inocua, pero juntas ralentizan cualquier cambio futuro.

Un ejemplo común: un handler decodifica JSON a una struct y devuelve 400 con un mensaje útil, otro devuelve 422 con una forma distinta, y un tercero registra errores en otro formato. Nada de eso rompe la compilación. Simplemente crea toma de decisiones constante y pequeñas reescrituras cada vez que añades algo.

Notas de dónde se siente el desorden:

  • Cuerpos de error diferentes entre endpoints, por lo que los clientes necesitan casos especiales.
  • Servicios que a veces devuelven errores crudos de la base de datos y otras veces errores “amigables”.
  • Deriva en los nombres (CreateUser, AddUser, RegisterUser) que hace que buscar sea más difícil.
  • Validación que se mueve por distintos lugares, así que los mismos bugs vuelven una y otra vez.

Por “scaffolding” aquí me refiero a una plantilla repetible para trabajo nuevo: dónde va el código, qué hace cada capa y qué aspecto tienen las respuestas. Es menos generar mucho código y más fijar una forma consistente.

Herramientas como Claude pueden ayudarte a scaffoldear nuevos endpoints rápido, pero solo siguen siendo útiles cuando tratas el patrón como una regla. Tú defines las reglas, revisas cada diff y ejecutas tests. El modelo rellena las partes estándar; no puede redefinir tu arquitectura.

Elige una división de capas: handler, service y acceso a datos

Una API en Go es fácil de escalar cuando cada petición sigue el mismo camino. Antes de empezar a generar endpoints, elige una división de capas y mantenla.

Responsabilidades, mantenidas simples

El trabajo del handler es solo HTTP: leer la petición, llamar al servicio y escribir la respuesta. No debería contener reglas de negocio, SQL ni lógica de “solo este caso especial”.

El service se encarga del caso de uso: reglas de negocio, decisiones y orquestación entre repositorios o llamadas externas. No debería conocer preocupaciones HTTP como códigos de estado, cabeceras o cómo se renderizan los errores.

El acceso a datos (repositorio/store) se ocupa de los detalles de persistencia. Traduce la intención del service a SQL/consultas/transacciones. No debería imponer reglas de negocio más allá de la integridad básica de los datos ni moldear respuestas de API.

Una checklist de separación práctica:

  • Handler: parsear la entrada, llamar al service, mapear errores a HTTP, escribir JSON
  • Service: aplicar reglas de negocio, llamar a repos, devolver resultados de dominio o errores tipados
  • Acceso a datos: ejecutar consultas, mapear filas a structs, devolver errores de almacenamiento
  • Compartido: tipos de error comunes y helpers de respuesta
  • Ninguna capa salta otra (el handler nunca llama directamente al repo)

Una regla para la validación

Elige una regla y no la dobles.

Un enfoque simple:

  • Los handlers hacen “validación de forma” (campos obligatorios, formato básico).
  • Los services hacen “validación de significado” (permisos, invariantes, estado).

Ejemplo: el handler comprueba que email está presente y tiene aspecto de email. El service comprueba que el email está permitido y no está ya en uso.

Qué se mueve entre capas

Decide pronto si los services devuelven tipos de dominio o DTOs.

Un defecto limpio es: los handlers usan DTOs de request/response, los services usan tipos de dominio y el handler mapea dominio a respuesta. Eso mantiene el service estable aunque cambie el contrato HTTP.

Si el mapeo parece pesado, mantenlo consistente de todos modos: que el service devuelva un tipo de dominio más un error tipado, y deja el moldeado JSON en el handler.

Define una respuesta de error estándar y un mapa de códigos de estado

Si quieres que los endpoints generados parezcan escritos por la misma persona, fija las respuestas de error temprano. La generación funciona mejor cuando el formato de salida es no negociable: una sola forma JSON, un mapa de códigos y una regla sobre qué se expone.

Empieza con un único sobre de error que cada endpoint devuelve en fallos. Mantenlo pequeño y predecible:

{
  "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..."
}

Usa code para las máquinas (estable y predecible) y message para humanos (corto y seguro). Pon datos estructurados opcionales en details. Para validación, un simple mapa details.fields es fácil de generar y fácil para que los clientes lo muestren junto a los inputs.

A continuación, escribe un mapa de códigos de estado y cúmplelo. Menos debate por endpoint, mejor. Si quieres tanto 400 como 422, haz la separación explícita:

  • bad_json -> 400 Bad Request (JSON malformado)
  • validation_failed -> 422 Unprocessable Content (JSON bien formado, campos inválidos)
  • not_found -> 404 Not Found
  • conflict -> 409 Conflict (clave duplicada, desajuste de versión)
  • unauthorized -> 401 Unauthorized
  • forbidden -> 403 Forbidden
  • internal -> 500 Internal Server Error

Decide qué registras frente a qué devuelves. Una buena regla: los clientes reciben un mensaje seguro y un request ID; los logs contienen el error completo y el contexto interno (SQL, payloads upstream, IDs de usuario) que nunca querrías filtrar.

Finalmente, estandariza request_id. Acepta un header de ID entrante si está presente (desde un API gateway); si no, genera uno en el borde (middleware). Adjúntalo al contexto, inclúyelo en los logs y devuélvelo en cada respuesta de error.

Estructura de carpetas y nombres que hacen predecible la generación

Si quieres que el scaffolding se mantenga consistente, la disposición de carpetas tiene que ser aburrida y repetible. Los generadores siguen patrones que pueden ver, pero se desvían cuando los archivos están dispersos o los nombres cambian por feature.

Elige una convención de nombres y no la dobles. Usa una palabra para cada cosa y manténla: handler, service, repo, request, response. Si la ruta es POST /users, nombra archivos y tipos alrededor de users y create (no a veces register, a veces addUser).

Una disposición simple que encaja con las capas:

internal/
  httpapi/
    handlers/
    users_handler.go
  services/
    users_service.go
  data/
    users_repo.go
  apitypes/
    users_types.go

Decide dónde viven los tipos compartidos, porque aquí es donde los proyectos suelen volverse desordenados. Una regla útil:

  • Los tipos de request/response de la API viven en internal/apitypes (coinciden con JSON y necesidades de validación).
  • Los tipos de dominio viven más cerca de la capa de servicios (coinciden con reglas de negocio).

Si un tipo tiene etiquetas JSON y está diseñado para clientes, trátalo como tipo de API.

Mantén las dependencias de los handlers mínimas y explícitas:

  • Handlers importan solo: routing/http, context, apitypes y services
  • Services importan: tipos de dominio y acceso a datos
  • Acceso a datos importa: driver de base de datos y helpers de consultas
  • Ningún handler importa el paquete de base de datos directamente

Escribe un documento corto de patrón en la raíz del repo (Markdown simple está bien). Incluye el árbol de carpetas, reglas de nombres y un pequeño flujo de ejemplo (handler -> service -> repo, y en qué archivo va cada pieza). Esta es la referencia exacta que pegas en tu generador para que los nuevos endpoints coincidan con la estructura cada vez.

Crea un endpoint de referencia que establezca el patrón

Reduce review back-and-forth
Iterate on naming, files, and error mapping with quick diffs in a chat workflow.

Antes de generar diez endpoints, crea uno que controles. Éste es el estándar de oro: el archivo al que señalas y dices, “el código nuevo debe verse así.” Puedes escribirlo desde cero o refactorizar uno existente hasta que coincida.

Mantén el handler delgado. Un movimiento que ayuda mucho: pon una interfaz entre el handler y el service para que el handler dependa de un contrato, no de una estructura concreta.

Añade pequeños comentarios en el endpoint de referencia solo donde el código generado podría confundirse en el futuro. Explica decisiones (por qué 400 vs 422, por qué el create devuelve 201, por qué ocultas errores internos detrás de un mensaje genérico). Evita comentarios que solo repitan el código.

Una vez que el endpoint de referencia funciona, extrae helpers para que cada nuevo endpoint tenga menos oportunidades de desviarse. Los helpers más reutilizables suelen ser:

  • Bind JSON y manejar cuerpos malformados
  • Validar entrada y devolver errores por campo
  • Escribir respuestas JSON de forma consistente
  • Mapear errores de dominio a códigos HTTP

Esto es lo que un “handler delgado + interfaz” puede parecer en la práctica:

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)
}

Fíjalo con un par de tests (incluso una pequeña tabla de tests para el mapeo de errores). La generación funciona mejor cuando tiene un objetivo limpio que imitar.

Paso a paso: haz que Claude genere un nuevo endpoint que coincida

La consistencia comienza con lo que pegas y lo que prohíbes. Para un endpoint nuevo, da dos cosas:

  1. Tu endpoint de referencia (el ejemplo “perfecto”)
  2. Una nota corta de patrón que nombre los paquetes, funciones y helpers que debe seguir

1) Proporciona contexto primero (referencia + reglas)

Incluye el handler, el método del service, tipos de request/response y cualquier helper compartido que use el endpoint. Luego explica el contrato en términos simples:

  • Ruta + método (ejemplo: POST /v1/widgets)
  • Campos JSON de request (obligatorios vs opcionales)
  • Forma del JSON de respuesta
  • Casos de error y códigos de estado
  • Archivos que esperas de vuelta (y solo esos archivos)

Sé explícito sobre lo que debe coincidir: nombres, rutas de paquetes y funciones helper (WriteJSON, BindJSON, WriteError, tu validador).

2) Pide la salida en la forma exacta que quieres

Un prompt ajustado evita refactors “útiles”. Por ejemplo:

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.

Si usas tests, pídelos explícitamente (y nombra el archivo de test). De lo contrario, el modelo puede saltárselos o inventar una configuración de test.

Haz una revisión rápida del diff tras la generación. Si modificó helpers compartidos, el registro del router o tu respuesta de error estándar, rechaza la salida y reitera las reglas de “no cambiar” más estrictamente.

Una plantilla de prompt reutilizable para scaffolds de endpoints consistentes

La salida es tan consistente como la entrada que le des. La forma más rápida de evitar código “casi correcto” es reutilizar una plantilla de prompt cada vez, con un pequeño snapshot de contexto tomado de tu repo.

Plantilla de prompt

Copia, pega y rellena los marcadores:

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.

Esto funciona porque fuerza tres cosas: un bloque de contexto (qué existe), un bloque de restricciones (qué no hacer) y ejemplos JSON concretos (para que las formas no se desvíen). La instrucción final es tu red de seguridad: si el modelo no está seguro, debe decirlo antes de comprometerse a un nuevo patrón.

Ejemplo realista: añadir un endpoint Create sin romper el estilo

Ship and test faster
Host your API after scaffolding so you can test real client behavior early.

Supongamos que quieres añadir un endpoint “Create project”. El objetivo es simple: aceptar un nombre, aplicar unas pocas reglas, guardarlo y devolver un ID nuevo. La parte difícil es mantener la misma separación handler-service-repo y el mismo JSON de error que ya usas.

Un flujo consistente se ve así:

  • Handler: bind JSON, validación básica de campos, llamar al servicio
  • Service: aplicar reglas de negocio (como unicidad), llamar al repo, devolver un resultado de dominio
  • Repo: escribir en Postgres, devolver el ID generado

Aquí está la petición que acepta el handler:

{ "name": "Roadmap", "owner_id": "u_123" }

En éxito, devuelve 201 Created. El ID debe venir de un único lugar siempre. Por ejemplo, deja que Postgres lo genere y que el repo lo devuelva:

{ "id": "p_456", "name": "Roadmap", "owner_id": "u_123", "created_at": "2026-01-09T12:34:56Z" }

Dos rutas de fallo realistas:

Si falla la validación (nombre faltante o demasiado corto), devuelve un error por campo usando tu forma estándar y el código de estado elegido:

{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request", "details": { "name": "must be at least 3 characters" } } }

Si el nombre debe ser único por propietario y el service encuentra un proyecto existente, devuelve 409 Conflict:

{ "error": { "code": "PROJECT_NAME_TAKEN", "message": "Project name already exists", "details": { "name": "Roadmap" } } }

Una decisión que mantiene el patrón limpio: el handler comprueba “¿esta petición tiene la forma correcta?” mientras que el service es dueño de “¿está esto permitido?”. Esa separación hace que los endpoints generados sean previsibles.

Errores comunes que rompen la consistencia (y cómo evitarlos)

La forma más rápida de perder consistencia es permitir que el generador improvise.

Un desvío común es una nueva forma de error. Un endpoint devuelve {error: "..."}, otro devuelve {message: "..."} y un tercero añade un objeto anidado. Arregla esto manteniendo un único sobre de error y un mapa de códigos en un solo lugar, y exige que los nuevos endpoints los reutilicen por ruta de import y nombre de función. Si el generador propone un nuevo campo, trátalo como un cambio de API, no como una conveniencia.

Otro desvío es el engorde del handler. Empieza pequeño: valida, luego comprueba permisos, luego consulta la BD, luego ramifica en reglas de negocio. Pronto cada handler se ve distinto. Mantén una regla: los handlers traducen HTTP a entradas y salidas tipadas; los services son dueños de las decisiones; el acceso a datos es dueño de las consultas.

Los desajustes de nombres también se acumulan. Si un endpoint usa CreateUserRequest y otro NewUserPayload, perderás tiempo buscando tipos y escribiendo adaptadores. Escoge un esquema de nombres y rechaza nombres nuevos salvo razón sólida.

Nunca devuelvas errores crudos de la base de datos a los clientes. Además de filtrar detalles, crea mensajes y códigos de estado inconsistentes. Envuelve los errores internos, registra la causa y devuelve un código público estable.

Evita añadir librerías nuevas “solo por conveniencia”. Cada validador extra, helper de router o paquete de errores se convierte en otro estilo que igualar.

Guardarraíles que previenen la mayoría de roturas:

  • Requerir que los nuevos endpoints reutilicen tipos de error y helpers existentes.
  • Mantener los handlers libres de reglas de negocio y acceso a BD.
  • Hacer cumplir una convención de nombres para request y response.
  • Mapear errores internos a códigos públicos, nunca errores crudos.
  • Añadir dependencias solo con una razón escrita y clara.

Si no puedes diffear dos endpoints y ver la misma forma (imports, flujo, manejo de errores), ajusta el prompt y regenera antes de mergear.

Lista rápida antes de mergear un endpoint generado

Grow without style drift
Scale endpoint generation across a larger API while keeping your baseline in charge.

Antes de mergear algo generado, comprueba la estructura primero. Si la forma es correcta, los bugs lógicos son más fáciles de detectar.

Comprobaciones de estructura:

  • El flujo del handler es consistente: bind input, validar, llamar al service, mapear errores de dominio a HTTP, escribir la respuesta.
  • El código del service contiene solo reglas de negocio: nada de HTTP ni JSON.
  • Las respuestas de éxito coinciden con el estilo de la casa (o un sobre compartido en todas partes o JSON directo en todas partes).
  • Las respuestas de error son uniformes: mismos campos JSON, mismos códigos, mismo comportamiento request_id.
  • Nombres y ubicación aburridos: nombres de archivos, funciones y rutas coinciden con endpoints existentes, y todo está formateado con gofmt.

Comprobaciones de comportamiento:

  • Ejecuta tests y añade al menos un test pequeño para una nueva rama de handler/service.
  • Confirma que fallos de validación devuelven el mismo código y estado que endpoints similares.
  • Fuerza un error conocido del service (como “not found” o “conflict”) y confirma el estado HTTP y la forma JSON.
  • Busca restos de copy-paste: ruta equivocada, mensaje de log incorrecto, nombres de DTO desajustados.
  • Compila y ejecuta el servidor localmente una vez para asegurar wiring e imports correctos.

Siguientes pasos: estandariza el patrón y luego escala la generación con seguridad

Trata tu patrón como un contrato compartido, no como una preferencia. Mantén el documento “cómo construimos endpoints” junto al código y conserva un endpoint de referencia que muestre el enfoque completo.

Aumenta la generación en lotes pequeños. Genera 2 o 3 endpoints que cubran distintos bordes (una lectura simple, un create con validación, una actualización con un caso de not-found). Luego para y refina. Si las revisiones siguen encontrando la misma deriva de estilo, actualiza el doc base y el endpoint de referencia antes de generar más.

Un ciclo que puedes repetir:

  • Escribe la base: nombres de archivos, nombres de funciones, structs de request/response, códigos de error y dónde ocurre la validación.
  • Mantén un endpoint “golden” y actualízalo primero cuando el patrón cambie.
  • Genera por lotes unos pocos endpoints, revisa consistencia y ajusta el prompt y el doc de patrón.
  • Refactoriza endpoints antiguos por bloques y mantén una ruta de rollback si cambia el comportamiento.
  • Mide una métrica por una semana: tiempo para añadir un endpoint, tasa de bugs tras merge o tiempo de revisión.

Si quieres un bucle build-review más ajustado, una plataforma vibe-coding como Koder.ai (koder.ai) puede ayudarte a scaffoldear e iterar rápido en un flujo de trabajo guiado por chat, y luego exportar el código una vez que coincida con tu estándar. La herramienta importa menos que la regla: tu baseline debe seguir estando al mando.

Preguntas frecuentes

What’s the fastest way to stop a Go API from getting inconsistent?

Bloquea una plantilla repetible desde temprano: una división de capas consistente (handler → service → data access), un sobres de error único y un mapa de códigos de estado. Luego usa un único “endpoint de referencia” como ejemplo que cada nuevo endpoint debe copiar.

What should a handler do (and not do)?

Mantén los handlers centrados en HTTP:

  • Bind/parse the request
  • Do basic “shape validation” (required fields, simple formats)
  • Call the service
  • Map typed errors to HTTP status codes
  • Write JSON using shared helpers

Si ves SQL, comprobaciones de permisos o ramificaciones de negocio en un handler, trasládalas al servicio.

What belongs in the service layer?

Coloca reglas de negocio y decisiones en el service:

  • Permisos y reglas de acceso
  • Invariantes (transiciones de estado, reglas de unicidad, comprobaciones de “permitido”)
  • Orquestación entre repositorios y llamadas externas

El service debe devolver resultados de dominio y errores tipados: sin códigos HTTP, sin dar forma a JSON.

What belongs in the data access/repository layer?

Mantén aisladas las preocupaciones de persistencia:

  • SQL/queries y transacciones
  • Mapear filas a structs
  • Devolver errores de almacenamiento (que el servicio pueda interpretar)

Evita codificar formatos de respuesta de API o imponer reglas de negocio en el repositorio, salvo la integridad básica de los datos.

Where should validation live?

Un valor por defecto simple:

  • Handlers validan la forma de la petición (campos faltantes, formato básico)
  • Services validan el significado (permisos, invariantes, estado)

Ejemplo: el handler comprueba que email está presente y parece un email; el service comprueba que esté permitido y que no esté ya en uso.

What should a standard API error response look like?

Usa un único sobre de error estándar en todas partes y mantenlo estable. Una forma práctica es:

  • code para máquinas (estable)
  • message para humanos (corto y seguro)
  • details para extras estructurados (por ejemplo errores por campo)
  • request_id para trazabilidad

Esto evita casos especiales en el cliente y hace que los endpoints generados sean previsibles.

How do I choose between 400 vs 422 vs 409 for errors?

Escribe un mapa de códigos de estado y síguelo siempre. Una división común:

  • 400 para JSON malformado (bad_json)
  • 422 para fallos de validación (validation_failed)
  • 404 para recursos inexistentes (not_found)
  • 409 para conflictos (duplicados/desajustes de versión)
  • 500 para fallos inesperados

La clave es la consistencia: no debatir por endpoint.

Should I ever return raw database errors to clients?

Devuelve errores públicos seguros y consistentes, y registra la causa real internamente.

  • Respuesta: code estable, message corto, más request_id
  • Logs: detalles completos del error (errores SQL, payloads upstream, IDs de usuario)

Así evitas filtrar detalles internos y diferencias aleatorias en los mensajes de error entre endpoints.

What is a reference endpoint, and why do I need one?

Crea un endpoint “golden” de referencia que controles y exige que los nuevos endpoints se ajusten a él:

  • Mismo flujo (bind → validate → service → error map → JSON)
  • Mismos helpers (BindJSON, WriteJSON, WriteError, etc.)
  • Misma estructura de carpetas y nombres

Añade un par de tests pequeños (incluso tests de tabla para el mapeo de errores) para fijar el patrón.

How do I prompt Claude to generate new endpoints without breaking my structure?

Dale al modelo contexto y restricciones estrictas:

  • Pega el endpoint de referencia y las reglas del patrón
  • Especifica ruta, ejemplos JSON de request/response y casos de error
  • Lista exactamente qué archivos puede generar
  • Di explícitamente qué no debe cambiar (router, formato de error, helpers)

Tras la generación, rechaza diffs que “mejoren” la arquitectura en lugar de seguir la base.

Related posts