Skip to main content

Generated Code Guide

This page explains what each generated file does and what you're expected to modify after generation.

File Overview

For goclarc module user --db postgres, six Go files and two SQL files are generated:

internal/modules/user/
entity.go
dto.go
repository.go
service.go
handler.go
routes.go
schemas/queries/
users.sql
db/migrations/
001_create_users.sql

With --swagger on both commands, two additional files are created:

docs/
swagger.go ← package docs: serves Swagger UI + raw spec (goclarc new --swagger)
openapi.yaml ← OpenAPI 3.0 spec, updated per module (goclarc module --swagger)

The database connection itself lives in internal/core/db/db.go, generated by goclarc new. Every module's NewRepository receives that single shared pool.


entity.go

Defines the domain model and the public API view.

// Entity is the internal domain representation.
type Entity struct {
ID string
Email string
Name string
CreatedAt time.Time
}

// View is the JSON API representation.
type View struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
CreatedAt string `json:"created_at"`
}

// ToView converts the domain entity to its public API view.
func (e *Entity) ToView() View {
return View{
ID: e.ID,
Email: e.Email,
Name: e.Name,
CreatedAt: e.CreatedAt.Format(time.RFC3339),
}
}

What you might add: Extra computed fields in ToView(), additional view types (e.g., ToOwnerView() with sensitive fields).


dto.go

Defines request structures for Create and Update operations.

// CreateRequest is bound from the POST request body.
type CreateRequest struct {
Email string `json:"email" binding:"required"`
Name string `json:"name" binding:"required"`
}

// UpdateRequest is bound from the PATCH request body.
// All fields are optional — only non-nil values are applied.
type UpdateRequest struct {
Email *string `json:"email"`
Name *string `json:"name"`
}

What you might add: Custom validation beyond binding:"required", field transformations before passing to the service.


repository.go

Defines the data access contract (interface) and the pgx implementation.

// Repository — the interface your service depends on.
type Repository interface {
Create(ctx context.Context, p CreateParams) (*Entity, error)
GetByID(ctx context.Context, id string) (*Entity, error)
List(ctx context.Context) ([]*Entity, error)
Update(ctx context.Context, p UpdateParams) (*Entity, error)
Delete(ctx context.Context, id string) error
}

// repository — the concrete implementation (unexported).
type repository struct {
pool *pgxpool.Pool
}

Queries are raw SQL strings embedded directly in the repository. pgx/v5 scans results into Entity fields via a shared scanEntity helper that accepts both pgx.Row and pgx.Rows.

Not-found cases wrap the apperr.ErrNotFound sentinel so the full error chain is intact for errors.Is:

if errors.Is(err, pgx.ErrNoRows) {
return nil, fmt.Errorf("user.repository.GetByID: %w", apperr.ErrNotFound)
}

The error middleware then maps this to a 404 using errors.Is(err, apperr.ErrNotFound) — no string matching involved.

What you might add: Additional query methods (e.g., ListByEmail, GetByUserID), pagination parameters to List.


service.go

Implements the business logic layer.

// Service — the interface your handler depends on.
type Service interface {
Create(ctx context.Context, req CreateRequest) (*Entity, error)
GetByID(ctx context.Context, id string) (*Entity, error)
List(ctx context.Context) ([]*Entity, error)
Update(ctx context.Context, id string, req UpdateRequest) (*Entity, error)
Delete(ctx context.Context, id string) error
}

What you should add: Real business logic — authorization checks, cascading operations, event emission, email sending, etc. The generated stubs just delegate to the repository.


handler.go

Gin HTTP handlers — one function per operation.

func (h *Handler) Create(c *gin.Context) {
var req CreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
_ = c.Error(err)
return
}
entity, err := h.service.Create(c.Request.Context(), req)
if err != nil {
_ = c.Error(err)
return
}
c.JSON(http.StatusCreated, gin.H{"success": true, "data": entity.ToView()})
}

Convention: Handlers never contain business logic. They parse, delegate, and respond. Errors always go to c.Error(err) — the global error middleware handles the response.

What you might add: Extra header parsing, pagination query params, auth context extraction (e.g., c.GetString("userID")).


routes.go

Wires all 5 endpoints onto a Gin router group.

func RegisterRoutes(rg *gin.RouterGroup, h *Handler, middlewares ...gin.HandlerFunc) {
g := rg.Group("/users", middlewares...)
g.POST("", h.Create)
g.GET("", h.List)
g.GET("/:id", h.GetByID)
g.PATCH("/:id", h.Update)
g.DELETE("/:id", h.Delete)
}

What you might add: Additional routes (e.g., bulk operations, nested resources), different middleware per route.


schemas/queries/users.sql

A reference SQL file with all CRUD queries for your table. Useful as a starting point if you later add custom queries to the repository.


db/migrations/001_create_<table>.sql

A CREATE TABLE statement derived from your schema fields. Apply it to your database before running the server:

psql $DATABASE_URL -f db/migrations/001_create_users.sql


docs/swagger.go (optional, --swagger)

Generated by goclarc new --swagger. Lives in the docs package alongside openapi.yaml and uses //go:embed openapi.yaml to embed the spec at compile time.

Exposes two endpoints via docs.RegisterRoutes(rg *gin.RouterGroup):

PathResponse
GET /docsSwagger UI (CDN, no extra dependencies)
GET /docs/openapi.yamlRaw OpenAPI 3.0 YAML

No external packages are added to go.mod — only Go stdlib (embed, net/http) and Gin, which is already present.

What you might add: Basic auth guard on the /docs group for production environments.


docs/openapi.yaml (optional, --swagger)

Generated by goclarc new --swagger as a project-level starter, and updated by goclarc module --swagger which writes a module-scoped spec to docs/<module>.openapi.yaml.

The module spec contains the full OpenAPI 3.0 definition for that module — paths, request/response schemas, required fields, nullable flags, and error responses — derived directly from the schema YAML. Copy or merge it into docs/openapi.yaml to keep the live spec up to date:

# Single module
cp docs/user.openapi.yaml docs/openapi.yaml

# Multiple modules (requires yq)
yq '. *+ load("docs/user.openapi.yaml") *+ load("docs/post.openapi.yaml")' \
docs/openapi.yaml > docs/merged.yaml && mv docs/merged.yaml docs/openapi.yaml

The spec is accurate by construction — it is generated from the same schema file that produces the handler, DTO, and entity code, so field names, types, and required constraints are always in sync at generation time.

What you might add: Authentication schemes (securitySchemes), shared server definitions, or extra examples per operation.


Error Responses

The generated error middleware (internal/middleware/error.go) maps errors to HTTP responses automatically:

SituationStatusResponse
AppError (typed sentinel)varies{"success":false,"error":{"code":"...","message":"..."}}
errors.Is(err, ErrNotFound)404{"success":false,"error":{"code":"NOT_FOUND","message":"..."}}
Any other error500{"success":false,"error":{"code":"INTERNAL_ERROR","message":"..."}}

The middleware uses errors.Is to traverse the full error chain — no fragile string matching.

The actual error message is always included — you'll never see a generic "an unexpected error occurred" in the response.


Regenerating

Use --force to overwrite existing files after changing a schema:

goclarc module user --db postgres --schema schemas/user.yaml --force

Use --reset to delete all generated files and start clean:

goclarc module user --db postgres --schema schemas/user.yaml --reset
caution

Both --force and --reset overwrite or delete generated files. Any custom additions (business logic in service.go, extra queries in repository.go) will be lost. Use --dry-run first to review what would change.