Code Generation
The Gofasta CLI includes a powerful code generation system that creates files following the project’s architecture (controllers → services → repositories → models). Every generated file follows consistent conventions and integrates with the existing project structure.
Layout awareness
Every gofasta g * command reads config.yaml’s project.layout field and emits files in the shape that matches your project. You never pick a layout on the generator command line — the CLI infers it once per invocation and writes the right paths.
What “layout-aware” means in practice
Compare the same command in each layout:
# In a project created with `gofasta new my-app` (or `--layout=layered`)
$ gofasta g scaffold Product name:string price:float
create: app/models/product.model.go
create: app/dtos/product.dtos.go
create: app/dtos/product.dtos_test.go
create: app/repositories/interfaces/product_repository.go
create: app/repositories/product.repository.go
create: app/services/interfaces/product_service.go
create: app/services/product.service.go
create: app/services/product_errors.go
create: app/services/product_inputs.go
create: app/rest/controllers/product.controller.go
create: app/rest/routes/product.routes.go
create: app/di/providers/product.go
patch: app/di/container.go (add ProductRepo/ProductService fields)
patch: app/di/wire.go (add providers.ProductSet to wire.Build)
patch: app/rest/routes/index.routes.go (register ProductRoutes under /api/v1)# Same command in a project created with `gofasta new my-app --layout=feature`
$ gofasta g scaffold Product name:string price:float
create: app/models/product.model.go # ← model stays layered
create: app/product/dtos.go
create: app/product/dtos_test.go
create: app/product/repository.go
create: app/product/repository_iface.go
create: app/product/service.go
create: app/product/service_iface.go
create: app/product/errors.go
create: app/product/inputs.go
create: app/product/controller.go
create: app/product/routes.go # exports RegisterRoutes(...)
create: app/product/wire.go # exports ProductSet
patch: app/di/container.go (add productpkg.ProductRepositoryInterface fields)
patch: app/di/wire.go (add productpkg.ProductSet to wire.Build)
patch: app/rest/routes/index.routes.go (register productpkg.RegisterRoutes)Same source code, same wire bindings, same HTTP routes — only the on-disk file shape differs.
Per-generator output: where things land
The exact path matrix per layout:
| Generator | Layered | Feature |
|---|---|---|
gofasta g model Product | app/models/product.model.go | app/models/product.model.go (model never moves) |
gofasta g dto Product | app/dtos/product.dtos.go | app/product/dtos.go |
gofasta g repository Product | app/repositories/product.repository.go + interfaces/product_repository.go | app/product/repository.go + repository_iface.go |
gofasta g service Product | app/services/product.service.go + interfaces/product_service.go + product_errors.go + product_inputs.go | app/product/service.go + service_iface.go + errors.go + inputs.go |
gofasta g controller Product | app/rest/controllers/product.controller.go | app/product/controller.go |
gofasta g route Product | app/rest/routes/product.routes.go (func ProductRoutes(...)) | app/product/routes.go (func RegisterRoutes(...)) |
gofasta g provider Product | app/di/providers/product.go | app/product/wire.go |
gofasta g scaffold Product | all of the above | all of the above |
gofasta g migration AddTaxRate ... | db/migrations/NNNNNN_*.up.sql + .down.sql | same |
gofasta g job cleanup-tokens "..." | app/jobs/cleanup_tokens.go | same — jobs aren’t per-resource |
gofasta g task send-welcome | app/tasks/send_welcome.task.go | same — tasks aren’t per-resource |
gofasta g email-template welcome | templates/emails/welcome.html | same |
gofasta g resolver Product | patches app/graphql/resolvers/resolver.go | same path, different import shape |
gofasta g mock / --all | scans app/services/interfaces/ and app/repositories/interfaces/ | scans app/*/ for *_iface.go files |
The patches to cross-cutting files (app/di/container.go, app/di/wire.go, app/rest/routes/index.routes.go, cmd/serve.go) happen in both layouts — only the import aliases and field/call qualifiers differ:
| Patch target | Layered version | Feature version |
|---|---|---|
container.go field types | repoInterfaces.ProductRepositoryInterface, svcInterfaces.ProductServiceInterface, *controllers.ProductController | productpkg.ProductRepositoryInterface, productpkg.ProductServiceInterface, *productpkg.ProductController |
wire.go provider set ref | providers.ProductSet | productpkg.ProductSet |
index.routes.go route call | ProductRoutes(api, config.ProductController) | productpkg.RegisterRoutes(api, config.ProductController) |
Modify-aware generators (g method, g field, g endpoint, etc.)
The modify-aware generators (gofasta g method, g field, g endpoint, g repo-method, g relation, g rename, g middleware) infer their target paths from the project layout exactly the same way as the create-style generators:
gofasta g method Product Archive id:uuid.UUID
# Layered:
# patches app/services/interfaces/product_service.go
# patches app/services/product.service.go
# Feature:
# patches app/product/service_iface.go
# patches app/product/service.goYou can override paths explicitly with the --interface-file and --impl-file flags when the inference doesn’t match (rare, but useful for non-standard project shapes).
How the CLI reads the layout
When you run a generator, the lookup is:
- Read
config.yaml’sproject.layoutfield via theconfigutil.ReadLayout()helper. - If unset (e.g. for projects scaffolded before this feature shipped), fall back to filesystem detection: presence of
app/models/⇒ layered. - If neither signal is present, default to layered.
The resolved layout flows into ScaffoldData.Layout, which every generator consults to build its destination path. The full implementation is in cli/internal/layout/; the layout-aware emit pipeline (which runs each generated file through the AST featurize transformer when emitting under feature mode) is in cli/internal/featurize/.
Common pitfalls
- Editing
config.yaml’sproject.layoutby hand. Don’t. The filesystem won’t match what the CLI thinks the layout is, and the nextgofasta gwill emit files in the wrong shape. To switch layouts, rungofasta refactor— it changes both the config value and the filesystem together. - Hand-
git mv-ing files between layered and feature locations. Same problem. The refactor command rewrites imports, regenerates Wire, and patches cross-cutting wiring files — moving files alone leaves the project broken in subtle ways. - Assuming
gofasta g scaffolddoes the same thing in both layouts. It produces different file paths and different import shapes. The semantic result (same routes, same wire graph, same HTTP behavior) is identical, but the diff is large.
Inspecting the active layout
Use gofasta refactor status to see which layout the current project is in and which migration target is available. It’s read-only and runs in any state:
$ gofasta refactor status
Layout: feature (from config.yaml)
Resources: 3
• User
• Product
• Order
Migration target: layered
Command: gofasta refactor layered --allThe Generate Command
All generators are accessed through gofasta generate (or the shorthand gofasta g):
gofasta g <generator> <Name> [field:type ...]The Name argument is automatically converted to the correct casing for each context: PascalCase for Go types, snake_case for file names and database tables, and camelCase for JSON fields.
Available Generators
| Generator | Command | What it creates |
|---|---|---|
| scaffold | gofasta g scaffold | All layers for a complete resource |
| model | gofasta g model | Database model + migration |
| service | gofasta g service | Service interface + implementation |
| controller | gofasta g controller | REST controller |
| repository | gofasta g repository | Repository interface + implementation |
| dto | gofasta g dto | Request/response DTOs |
| migration | gofasta g migration | Empty migration pair (up + down) |
| route | gofasta g route | Route registration file |
| resolver | gofasta g resolver | Regenerate GraphQL resolver stubs |
| provider | gofasta g provider | Wire DI provider set |
| job | gofasta g job | Cron job definition |
| task | gofasta g task | Async task for the queue |
| email-template | gofasta g email-template | HTML email template |
Scaffold: The Full Resource Generator
The scaffold command is the most commonly used generator. It creates every layer of a resource and wires it into the project:
gofasta g scaffold Product name:string price:float description:text active:boolGenerated Files
| File | Location | Description |
|---|---|---|
| Model | app/models/product.model.go | GORM model with BaseModelImpl |
| Up migration | db/migrations/000006_create_products.up.sql | CREATE TABLE statement |
| Down migration | db/migrations/000006_create_products.down.sql | DROP TABLE statement |
| Repository interface | app/repositories/interfaces/product_repository.go | CRUD method signatures |
| Repository impl | app/repositories/product.repository.go | GORM implementation |
| Service interface | app/services/interfaces/product_service.go | Business logic signatures |
| Service impl | app/services/product.service.go | Business logic implementation |
| DTOs | app/dtos/product.dtos.go | Create/Update request + response types |
| Controller | app/rest/controllers/product.controller.go | HTTP handlers for CRUD |
| Routes | app/rest/routes/product.routes.go | Route registrations |
| Provider | app/di/providers/product.go | Wire provider set |
Patched Files
The scaffold command also modifies existing files to integrate the new resource:
| File | What changes |
|---|---|
app/di/container.go | Adds ProductService and ProductController fields |
app/di/wire.go | Adds ProductSet to the Wire build |
app/rest/routes/index.routes.go | Registers Product routes |
cmd/serve.go | Wires ProductController into the route config |
After scaffolding, run gofasta wire to regenerate the Wire DI container, then gofasta migrate up to create the database table.
Field Types
Fields are specified as name:type pairs. The type determines the Go type, SQL column type, and GraphQL type:
| Type | Go type | SQL type (Postgres) | SQL type (MySQL) | GraphQL type |
|---|---|---|---|---|
string | string | VARCHAR(255) | VARCHAR(255) | String |
text | string | TEXT | TEXT | String |
int | int | INTEGER | INT | Int |
float | float64 | DECIMAL(10,2) | DECIMAL(10,2) | Float |
bool | bool | BOOLEAN | TINYINT(1) | Boolean |
uuid | uuid.UUID | UUID | CHAR(36) | ID |
time | time.Time | TIMESTAMP | DATETIME | DateTime |
SQL types are automatically adapted for the database driver configured in config.yaml.
Individual Generators
Model
Generates the model struct and migration files:
gofasta g model Category name:string description:textCreates:
app/models/category.model.godb/migrations/000007_create_categories.up.sqldb/migrations/000007_create_categories.down.sql
The model automatically embeds models.BaseModelImpl for standard fields.
Service
Generates the service interface and implementation:
gofasta g service NotificationCreates:
app/services/interfaces/notification_service.goapp/services/notification.service.go
The service implementation receives a repository through its constructor.
Controller
Generates an HTTP controller:
gofasta g controller PaymentCreates:
app/rest/controllers/payment.controller.go
The controller receives a service through its constructor and includes stub CRUD methods.
Repository
Generates the repository interface and GORM implementation:
gofasta g repository OrderCreates:
app/repositories/interfaces/order_repository.goapp/repositories/order.repository.go
DTO
Generates request and response data transfer objects:
gofasta g dto Invoice amount:float status:string due_date:timeCreates:
app/dtos/invoice.dtos.go
Migration
Generates an empty migration pair for custom SQL:
gofasta g migration add_index_to_productsCreates:
db/migrations/000008_add_index_to_products.up.sqldb/migrations/000008_add_index_to_products.down.sql
Both files are empty — you write the SQL yourself. This is useful for schema changes that are not a simple table creation.
Route
Generates a route registration file:
gofasta g route SubscriptionCreates:
app/rest/routes/subscription.routes.go
Resolver
Regenerates GraphQL resolver stubs from schema files:
gofasta g resolverThis runs gqlgen to regenerate resolvers. Existing implementations are preserved.
Provider
Generates a Wire dependency injection provider:
gofasta g provider AnalyticsCreates:
app/di/providers/analytics.go
Job
Generates a cron job definition:
gofasta g job CleanupExpiredTokensCreates:
app/jobs/cleanup_expired_tokens.job.go
Task
Generates an async task for the queue:
gofasta g task SendWelcomeEmailCreates:
app/jobs/send_welcome_email.task.go
Email Template
Generates an HTML email template:
gofasta g email-template order-confirmationCreates:
templates/emails/order-confirmation.html
Customizing Generated Code
All generated code is plain Go — there are no runtime dependencies on the generator. After generation, you own the code and can modify it freely.
Common customizations:
- Add validation rules to DTOs using
validatestruct tags - Add indexes to models using
gormstruct tags - Add custom repository methods beyond the standard CRUD
- Add business logic to services
- Add custom endpoints to controllers and routes
Workflow Example
A typical workflow for adding a new feature:
# 1. Generate the full resource
gofasta g scaffold Order total:float status:string user_id:uuid
# 2. Regenerate Wire DI container
gofasta wire
# 3. Run migrations
gofasta migrate up
# 4. Customize the generated code
# - Add validation to app/dtos/order.dtos.go
# - Add business logic to app/services/order.service.go
# - Add auth middleware to app/rest/routes/order.routes.go
# 5. Test
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"total": 99.99, "status": "pending", "user_id": "..."}'