Skip to Content
DocumentationGuidesCode Generation

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:

GeneratorLayeredFeature
gofasta g model Productapp/models/product.model.goapp/models/product.model.go (model never moves)
gofasta g dto Productapp/dtos/product.dtos.goapp/product/dtos.go
gofasta g repository Productapp/repositories/product.repository.go + interfaces/product_repository.goapp/product/repository.go + repository_iface.go
gofasta g service Productapp/services/product.service.go + interfaces/product_service.go + product_errors.go + product_inputs.goapp/product/service.go + service_iface.go + errors.go + inputs.go
gofasta g controller Productapp/rest/controllers/product.controller.goapp/product/controller.go
gofasta g route Productapp/rest/routes/product.routes.go (func ProductRoutes(...))app/product/routes.go (func RegisterRoutes(...))
gofasta g provider Productapp/di/providers/product.goapp/product/wire.go
gofasta g scaffold Productall of the aboveall of the above
gofasta g migration AddTaxRate ...db/migrations/NNNNNN_*.up.sql + .down.sqlsame
gofasta g job cleanup-tokens "..."app/jobs/cleanup_tokens.gosame — jobs aren’t per-resource
gofasta g task send-welcomeapp/tasks/send_welcome.task.gosame — tasks aren’t per-resource
gofasta g email-template welcometemplates/emails/welcome.htmlsame
gofasta g resolver Productpatches app/graphql/resolvers/resolver.gosame path, different import shape
gofasta g mock / --allscans 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 targetLayered versionFeature version
container.go field typesrepoInterfaces.ProductRepositoryInterface, svcInterfaces.ProductServiceInterface, *controllers.ProductControllerproductpkg.ProductRepositoryInterface, productpkg.ProductServiceInterface, *productpkg.ProductController
wire.go provider set refproviders.ProductSetproductpkg.ProductSet
index.routes.go route callProductRoutes(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.go

You 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:

  1. Read config.yaml’s project.layout field via the configutil.ReadLayout() helper.
  2. If unset (e.g. for projects scaffolded before this feature shipped), fall back to filesystem detection: presence of app/models/ ⇒ layered.
  3. 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’s project.layout by hand. Don’t. The filesystem won’t match what the CLI thinks the layout is, and the next gofasta g will emit files in the wrong shape. To switch layouts, run gofasta 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 scaffold does 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 --all

The 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

GeneratorCommandWhat it creates
scaffoldgofasta g scaffoldAll layers for a complete resource
modelgofasta g modelDatabase model + migration
servicegofasta g serviceService interface + implementation
controllergofasta g controllerREST controller
repositorygofasta g repositoryRepository interface + implementation
dtogofasta g dtoRequest/response DTOs
migrationgofasta g migrationEmpty migration pair (up + down)
routegofasta g routeRoute registration file
resolvergofasta g resolverRegenerate GraphQL resolver stubs
providergofasta g providerWire DI provider set
jobgofasta g jobCron job definition
taskgofasta g taskAsync task for the queue
email-templategofasta g email-templateHTML 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:bool

Generated Files

FileLocationDescription
Modelapp/models/product.model.goGORM model with BaseModelImpl
Up migrationdb/migrations/000006_create_products.up.sqlCREATE TABLE statement
Down migrationdb/migrations/000006_create_products.down.sqlDROP TABLE statement
Repository interfaceapp/repositories/interfaces/product_repository.goCRUD method signatures
Repository implapp/repositories/product.repository.goGORM implementation
Service interfaceapp/services/interfaces/product_service.goBusiness logic signatures
Service implapp/services/product.service.goBusiness logic implementation
DTOsapp/dtos/product.dtos.goCreate/Update request + response types
Controllerapp/rest/controllers/product.controller.goHTTP handlers for CRUD
Routesapp/rest/routes/product.routes.goRoute registrations
Providerapp/di/providers/product.goWire provider set

Patched Files

The scaffold command also modifies existing files to integrate the new resource:

FileWhat changes
app/di/container.goAdds ProductService and ProductController fields
app/di/wire.goAdds ProductSet to the Wire build
app/rest/routes/index.routes.goRegisters Product routes
cmd/serve.goWires 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:

TypeGo typeSQL type (Postgres)SQL type (MySQL)GraphQL type
stringstringVARCHAR(255)VARCHAR(255)String
textstringTEXTTEXTString
intintINTEGERINTInt
floatfloat64DECIMAL(10,2)DECIMAL(10,2)Float
boolboolBOOLEANTINYINT(1)Boolean
uuiduuid.UUIDUUIDCHAR(36)ID
timetime.TimeTIMESTAMPDATETIMEDateTime

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:text

Creates:

  • app/models/category.model.go
  • db/migrations/000007_create_categories.up.sql
  • db/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 Notification

Creates:

  • app/services/interfaces/notification_service.go
  • app/services/notification.service.go

The service implementation receives a repository through its constructor.

Controller

Generates an HTTP controller:

gofasta g controller Payment

Creates:

  • 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 Order

Creates:

  • app/repositories/interfaces/order_repository.go
  • app/repositories/order.repository.go

DTO

Generates request and response data transfer objects:

gofasta g dto Invoice amount:float status:string due_date:time

Creates:

  • app/dtos/invoice.dtos.go

Migration

Generates an empty migration pair for custom SQL:

gofasta g migration add_index_to_products

Creates:

  • db/migrations/000008_add_index_to_products.up.sql
  • db/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 Subscription

Creates:

  • app/rest/routes/subscription.routes.go

Resolver

Regenerates GraphQL resolver stubs from schema files:

gofasta g resolver

This runs gqlgen to regenerate resolvers. Existing implementations are preserved.

Provider

Generates a Wire dependency injection provider:

gofasta g provider Analytics

Creates:

  • app/di/providers/analytics.go

Job

Generates a cron job definition:

gofasta g job CleanupExpiredTokens

Creates:

  • app/jobs/cleanup_expired_tokens.job.go

Task

Generates an async task for the queue:

gofasta g task SendWelcomeEmail

Creates:

  • app/jobs/send_welcome_email.task.go

Email Template

Generates an HTML email template:

gofasta g email-template order-confirmation

Creates:

  • 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 validate struct tags
  • Add indexes to models using gorm struct 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": "..."}'

Next Steps

Last updated on