Skip to content

Why Design-first

Most Go web frameworks are code-first. You write Go structs with tags, register handlers, and the API shape lives across many files. craftgo flips that: write the API spec first, generate the rest.

The problem with code-first

In a typical Gin or Echo project, the API surface lives in three places:

  1. Go structs with tags - request/response shapes, validation rules, JSON field names
  2. Handler code - HTTP method, path, middleware
  3. OpenAPI spec - usually a separate swagger.yaml you maintain by hand

These three drift apart. You add a field to the struct but forget to update the OpenAPI. You change a path in the handler but the spec still shows the old route. Your TypeScript clients break in production.

The design-first answer

craftgo collapses the three sources into one. The DSL is the contract. Everything else generates from it.

craftgo
type User {
    id    string
    name  string  @length(1, 80)
    email string  @format(email)
}

@prefix("/v1")
service UserService {
    post CreateUser /users {
        request  CreateUserReq
        response User
    }
}

From this one definition you get:

  • Go struct User with field tags
  • Validate() method that enforces the rules
  • HTTP handler at POST /v1/users
  • OpenAPI spec entry for the endpoint
  • Component schema in OpenAPI for User

Add a field once, in the DSL. Everything updates.

What you do not write

  • json:"name,omitempty" tags
  • Manual if req.Name == "" { return ... } checks
  • Hand-rolled OpenAPI YAML
  • Route registration boilerplate
  • Type definitions duplicated across HTTP handler and OpenAPI

What you do write

  • The DSL file (the contract)
  • Business logic in internal/service/... (your code)

The line between framework and your code is sharp. Generated files start with // Code generated by craftgo. DO NOT EDIT.. Logic stubs are scaffold-once and stay yours forever.

Tooling that comes with the contract

Because the DSL is structured, tools can read it:

  • LSP: completion of decorators based on field type, hover that shows decorator documentation, diagnostics that catch type mismatches before code generation.
  • Codegen drift detection: make gen-diff re-runs codegen and fails the build if the working tree changed. Catches "I forgot to regen" in CI.
  • Spec validation: the generated OpenAPI is valid OAS 3.1. Pass it to spectral lint or redocly for further checks.

When code-first is better

Design-first does not fit every project:

  • One-off scripts where the API surface is tiny.
  • Highly dynamic dispatch where routes are computed at runtime.
  • Glue code wrapping an external service whose schema is fluid.

For typical CRUD-shaped services with a stable contract - most production services - design-first wins on consistency, refactor speed, and onboarding clarity.

Eject is always available

The DSL produces plain Go. If you decide craftgo is not the right tool a year from now, the generated files are idiomatic and can be hand-maintained. There is no runtime hook into framework-internal types. Drop the DSL, keep the Go.

Released under the MIT License.