Skip to content

Project Structure

A craftgo project keeps the contract under design/ and the generated code under internal/. You write only the DSL files and the business logic stubs.

At a glance

What you writeWhere
DSL filesdesign/<package>/*.craftgo
Codegen configdesign/craftgo.design.yaml
Business logicinternal/service/<service>/<method>.go
Middleware implementationsinternal/middleware/<name>-middleware.go
Custom config fieldsconfig/config.go
Runtime valuesconfig/config.yaml
What craftgo writesRegenerated each run?
internal/types/yes
internal/transport/yes
internal/routes/yes
docs/openapi.yamlyes
internal/service/gen-once
internal/middleware/gen-once
svccontext/svccontext.gogen-once
config/gen-once
main.gogen-once

Layout after craftgo gen

my-app/
├── design/                              YOU WRITE
│   ├── craftgo.design.yaml              codegen config
│   └── users/
│       └── service.craftgo              DSL file

├── internal/                            generated + stubs
│   ├── types/
│   │   └── design/                      one subfolder per design package
│   │       ├── types.go                 generated structs
│   │       ├── validate.go              generated Validate() methods
│   │       ├── enums.go                 generated enum types
│   │       └── errors.go                generated typed errors
│   ├── transport/
│   │   └── user-service/                one subfolder per service
│   │       ├── create-user.go           generated HTTP handler factory
│   │       └── errors.go                generated writeError helper
│   ├── service/
│   │   └── user-service/
│   │       └── create-user.go           gen-once stub - YOU FILL
│   ├── routes/
│   │   ├── routes.go                    umbrella RegisterAll
│   │   └── user-service/routes.go       per-service registration
│   └── middleware/
│       └── auth-required-middleware.go  gen-once stub per declared middleware

├── svccontext/
│   └── svccontext.go                    gen-once dependency container

├── config/                              gen-once
│   ├── config.go                        Go struct + loader
│   ├── config.yaml                      runtime values
│   └── example.config.yaml              committed reference

├── docs/
│   └── openapi.yaml                     generated OpenAPI spec

├── main.go                              gen-once entry point
├── go.mod
└── go.sum

What is generated every run

These files start with // Code generated by craftgo. DO NOT EDIT. and are overwritten on every craftgo gen:

  • internal/types/<pkg>/types.go
  • internal/types/<pkg>/validate.go
  • internal/types/<pkg>/errors.go
  • internal/types/<pkg>/enums.go
  • internal/transport/<svc>/<method>.go
  • internal/transport/<svc>/errors.go
  • internal/routes/routes.go
  • internal/routes/<svc>/routes.go
  • svccontext/middlewares.go (sits next to svccontext.go)
  • docs/openapi.yaml

If you edit these files, your changes vanish next regen.

What is gen-once

These files are written when missing and never touched again:

  • internal/service/<svc>/<method>.go - business logic stub
  • internal/middleware/<name>-middleware.go - middleware stub
  • svccontext/svccontext.go - dependency container
  • config/config.go - config struct + loader
  • config/config.yaml - local config values
  • config/example.config.yaml - committed reference
  • main.go - entry point

You own these. The framework leaves them alone.

Configuration

design/craftgo.design.yaml controls codegen output. The defaults match the layout above:

yaml
output:
  types:      ./internal/types
  transport:    ./internal/transport
  routes:     ./internal/routes
  service:      ./internal/service
  middleware: ./internal/middleware
  svccontext: ./svccontext/svccontext.go
  openapi:    ./docs/openapi.yaml
  config:     ./config
  main:       ./main.go

openapi:
  title:    My API
  version:  1.0.0
  basePath: /api

Override any path to relocate the corresponding artifact. See Configuration for the full reference.

Force-regenerate

To regen from scratch:

bash
rm -rf internal/types internal/transport internal/routes docs/openapi.yaml
craftgo gen design

Gen-once files (internal/service/, internal/middleware/, svccontext/, config/, main.go) survive rm-and-regen because craftgo only writes them when missing.

Multiple packages

Each subfolder under design/ becomes a separate Go package. Files in the same subfolder share that package and reference each other directly.

design/
├── craftgo.design.yaml
├── shared/
│   └── common.craftgo                   package shared
├── users/
│   ├── service.craftgo                  package design (or design.users)
│   └── errors.craftgo
└── orders/
    └── service.craftgo

Cross-package types use import:

craftgo
package design

import "shared"

type User {
    contact shared.Contact
}

The codegen wires the matching Go imports automatically. Each design subfolder produces a corresponding subfolder under internal/types/.

Released under the MIT License.