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 write | Where |
|---|---|
| DSL files | design/<package>/*.craftgo |
| Codegen config | design/craftgo.design.yaml |
| Business logic | internal/service/<service>/<method>.go |
| Middleware implementations | internal/middleware/<name>-middleware.go |
| Custom config fields | config/config.go |
| Runtime values | config/config.yaml |
| What craftgo writes | Regenerated each run? |
|---|---|
internal/types/ | yes |
internal/transport/ | yes |
internal/routes/ | yes |
docs/openapi.yaml | yes |
internal/service/ | gen-once |
internal/middleware/ | gen-once |
svccontext/svccontext.go | gen-once |
config/ | gen-once |
main.go | gen-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.sumWhat 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.gointernal/types/<pkg>/validate.gointernal/types/<pkg>/errors.gointernal/types/<pkg>/enums.gointernal/transport/<svc>/<method>.gointernal/transport/<svc>/errors.gointernal/routes/routes.gointernal/routes/<svc>/routes.gosvccontext/middlewares.go(sits next tosvccontext.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 stubinternal/middleware/<name>-middleware.go- middleware stubsvccontext/svccontext.go- dependency containerconfig/config.go- config struct + loaderconfig/config.yaml- local config valuesconfig/example.config.yaml- committed referencemain.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:
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: /apiOverride any path to relocate the corresponding artifact. See Configuration for the full reference.
Force-regenerate
To regen from scratch:
rm -rf internal/types internal/transport internal/routes docs/openapi.yaml
craftgo gen designGen-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.craftgoCross-package types use import:
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/.