Code Generation
Goa’s code generation transforms your design into production-ready code. Rather than just scaffolding, Goa generates complete, runnable service implementations that follow best practices and maintain consistency across your entire API.
Command Line Tools
Installation
go install goa.design/goa/v3/cmd/goa@latest
Commands
All commands expect Go package import paths, not filesystem paths:
# ✅ Correct: using Go package import path
goa gen goa.design/examples/calc/design
# ❌ Incorrect: using filesystem path
goa gen ./design
Generate Code (goa gen)
goa gen <design-package-import-path> [-o <output-dir>]
The primary command for code generation:
- Processes your design package and generates implementation code
- Recreates the entire
gen/directory from scratch each time - Run after every design change
Create Example (goa example)
goa example <design-package-import-path> [-o <output-dir>]
A scaffolding command:
- Creates a one-time example implementation
- Generates handler stubs with example logic
- Run once when starting a new project
- Will NOT overwrite existing custom implementation
Show Version
goa version
Development Workflow
- Create initial design
- Run
goa gento generate base code - Run
goa exampleto create implementation stubs - Implement your service logic
- Run
goa genafter every design change
Best Practice: Commit generated code to version control rather than generating during CI/CD. This ensures reproducible builds and allows tracking changes in generated code.
Generation Process
When you run goa gen, Goa follows a systematic process:
1. Bootstrap Phase
Goa creates a temporary main.go that:
- Imports Goa packages and your design package
- Runs DSL evaluation
- Triggers code generation
2. Design Evaluation
- DSL functions execute to create expression objects
- Expressions combine into a complete API model
- Relationships between expressions are established
- Design rules and constraints are validated
3. Code Generation
- Validated expressions pass to code generators
- Templates render to produce code files
- Output writes to the
gen/directory
Generated Code Structure
A typical generated project:
myservice/
├── cmd/ # Generated example commands
│ └── calc/
│ ├── grpc.go
│ └── http.go
├── design/ # Your design files
│ └── design.go
├── gen/ # Generated code (don't edit)
│ ├── calc/ # Service-specific code
│ │ ├── client.go
│ │ ├── endpoints.go
│ │ └── service.go
│ ├── http/ # HTTP transport layer
│ │ ├── calc/
│ │ │ ├── client/
│ │ │ └── server/
│ │ └── openapi.json
│ └── grpc/ # gRPC transport layer
│ └── calc/
│ ├── client/
│ ├── server/
│ └── pb/
└── myservice.go # Your service implementation
Service Interfaces
Generated in gen/<service>/service.go:
// Service interface defines the API contract
type Service interface {
Add(context.Context, *AddPayload) (res int, err error)
Multiply(context.Context, *MultiplyPayload) (res int, err error)
}
// Payload types
type AddPayload struct {
A int32
B int32
}
// Constants for observability
const ServiceName = "calc"
var MethodNames = [2]string{"add", "multiply"}
Endpoint Layer
Generated in gen/<service>/endpoints.go:
// Endpoints wraps service methods in transport-agnostic endpoints
type Endpoints struct {
Add goa.Endpoint
Multiply goa.Endpoint
}
// NewEndpoints creates endpoints from service implementation
func NewEndpoints(s Service) *Endpoints {
return &Endpoints{
Add: NewAddEndpoint(s),
Multiply: NewMultiplyEndpoint(s),
}
}
// Use applies middleware to all endpoints
func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
e.Add = m(e.Add)
e.Multiply = m(e.Multiply)
}
Endpoint middleware example:
func LoggingMiddleware(next goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req any) (res any, err error) {
log.Printf("request: %v", req)
res, err = next(ctx, req)
log.Printf("response: %v", res)
return
}
}
endpoints.Use(LoggingMiddleware)
Client Code
Generated in gen/<service>/client.go:
// Client provides typed methods for service calls
type Client struct {
AddEndpoint goa.Endpoint
MultiplyEndpoint goa.Endpoint
}
func NewClient(add, multiply goa.Endpoint) *Client {
return &Client{
AddEndpoint: add,
MultiplyEndpoint: multiply,
}
}
func (c *Client) Add(ctx context.Context, p *AddPayload) (res int, err error) {
ires, err := c.AddEndpoint(ctx, p)
if err != nil {
return
}
return ires.(int), nil
}
HTTP Code Generation
Server Implementation
Generated in gen/http/<service>/server/server.go:
func New(
e *calc.Endpoints,
mux goahttp.Muxer,
decoder func(*http.Request) goahttp.Decoder,
encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
errhandler func(context.Context, http.ResponseWriter, error),
formatter func(ctx context.Context, err error) goahttp.Statuser,
) *Server
// Server exposes handlers for modification
type Server struct {
Mounts []*MountPoint
Add http.Handler
Multiply http.Handler
}
// Use applies HTTP middleware to all handlers
func (s *Server) Use(m func(http.Handler) http.Handler)
Complete server setup:
func main() {
svc := calc.New()
endpoints := gencalc.NewEndpoints(svc)
mux := goahttp.NewMuxer()
server := genhttp.New(
endpoints,
mux,
goahttp.RequestDecoder,
goahttp.ResponseEncoder,
nil, nil)
genhttp.Mount(mux, server)
http.ListenAndServe(":8080", mux)
}
Client Implementation
Generated in gen/http/<service>/client/client.go:
func NewClient(
scheme string,
host string,
doer goahttp.Doer,
enc func(*http.Request) goahttp.Encoder,
dec func(*http.Response) goahttp.Decoder,
restoreBody bool,
) *Client
Complete client setup:
func main() {
httpClient := genclient.NewClient(
"http",
"localhost:8080",
http.DefaultClient,
goahttp.RequestEncoder,
goahttp.ResponseDecoder,
false,
)
client := gencalc.NewClient(
httpClient.Add(),
httpClient.Multiply(),
)
result, err := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
}
gRPC Code Generation
Protobuf Definition
Generated in gen/grpc/<service>/pb/:
syntax = "proto3";
package calc;
service Calc {
rpc Add (AddRequest) returns (AddResponse);
rpc Multiply (MultiplyRequest) returns (MultiplyResponse);
}
message AddRequest {
int64 a = 1;
int64 b = 2;
}
Server Implementation
func main() {
svc := calc.New()
endpoints := gencalc.NewEndpoints(svc)
svr := grpc.NewServer()
gensvr := gengrpc.New(endpoints, nil)
genpb.RegisterCalcServer(svr, gensvr)
lis, _ := net.Listen("tcp", ":8080")
svr.Serve(lis)
}
Client Implementation
func main() {
conn, _ := grpc.Dial("localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()))
defer conn.Close()
grpcClient := genclient.NewClient(conn)
client := gencalc.NewClient(
grpcClient.Add(),
grpcClient.Multiply(),
)
result, _ := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
}
Customization
Type Generation Control
Force generation of types not directly referenced by methods:
var MyType = Type("MyType", func() {
// Force generation in specific services
Meta("type:generate:force", "service1", "service2")
// Or force generation in all services
Meta("type:generate:force")
Attribute("name", String)
})
Package Organization
Generate types in a shared package:
var CommonType = Type("CommonType", func() {
Meta("struct:pkg:path", "types")
Attribute("id", String)
})
Creates:
gen/
└── types/
└── common_type.go
Field Customization
var Message = Type("Message", func() {
Attribute("id", String, func() {
// Override field name
Meta("struct:field:name", "ID")
// Add custom struct tags
Meta("struct:tag:json", "id,omitempty")
Meta("struct:tag:msgpack", "id,omitempty")
// Override type
Meta("struct:field:type", "bson.ObjectId", "github.com/globalsign/mgo/bson", "bson")
})
})
Protocol Buffer Customization
var MyType = Type("MyType", func() {
// Override protobuf message name
Meta("struct:name:proto", "CustomProtoType")
Field(1, "status", Int32, func() {
// Override protobuf field type
Meta("struct:field:proto", "int32")
})
// Use Google's timestamp type
Field(2, "created_at", String, func() {
Meta("struct:field:proto",
"google.protobuf.Timestamp",
"google/protobuf/timestamp.proto",
"Timestamp",
"google.golang.org/protobuf/types/known/timestamppb")
})
})
// Specify protoc include paths
var _ = API("calc", func() {
Meta("protoc:include", "/usr/include", "/usr/local/include")
})
OpenAPI Customization
var _ = API("MyAPI", func() {
// Control generation
Meta("openapi:generate", "false")
// Format JSON output
Meta("openapi:json:prefix", " ")
Meta("openapi:json:indent", " ")
// Disable example generation
Meta("openapi:example", "false")
})
var _ = Service("UserService", func() {
// Add tags
HTTP(func() {
Meta("openapi:tag:Users")
Meta("openapi:tag:Backend:desc", "Backend API Operations")
})
Method("CreateUser", func() {
// Custom operation ID
Meta("openapi:operationId", "{service}.{method}")
// Custom summary
Meta("openapi:summary", "Create a new user")
HTTP(func() {
// Add extensions
Meta("openapi:extension:x-rate-limit", `{"rate": 100}`)
POST("/users")
})
})
})
var User = Type("User", func() {
// Override type name in OpenAPI spec
Meta("openapi:typename", "CustomUser")
})
Types and Validation
Validation Enforcement
Goa validates data at system boundaries:
- Server-side: Validates incoming requests
- Client-side: Validates incoming responses
- Internal code: Trusted to maintain invariants
Pointer Rules for Struct Fields
| Properties | Payload/Result | Request Body (Server) | Response Body (Server) |
|---|---|---|---|
| Required OR Default | Direct (-) | Pointer (*) | Direct (-) |
| Not Required, No Default | Pointer (*) | Pointer (*) | Pointer (*) |
Special types:
- Objects (structs): Always use pointers
- Arrays and Maps: Never use pointers (already reference types)
Example:
type Person struct {
Name string // required, direct value
Age *int // optional, pointer
Hobbies []string // array, no pointer
Metadata map[string]string // map, no pointer
}
Default Value Handling
- Marshaling: Default values initialize nil arrays/maps
- Unmarshaling: Default values apply to missing optional fields (not missing required fields)
Views and Result Types
Views control how result types are rendered in responses.
How Views Work
- Service method includes a view parameter
- A views package is generated at the service level
- View-specific validation is automatically generated
Server-Side Response
- Viewed result type is marshalled
- Nil attributes are omitted
- View name is passed in “Goa-View” header
Client-Side Response
- Response is unmarshalled
- Transformed into viewed result type
- View name extracted from “Goa-View” header
- View-specific validation performed
- Converted back to service result type
Default View
If no views are defined, Goa adds a “default” view that includes all basic fields.
Plugin System
Goa’s plugin system extends code generation. Plugins can:
- Add New DSLs - Additional design language constructs
- Modify Generated Code - Inspect and modify files, add new files
Example using the CORS plugin:
import (
. "goa.design/goa/v3/dsl"
cors "goa.design/plugins/v3/cors/dsl"
)
var _ = Service("calc", func() {
cors.Origin("/.*localhost.*/", func() {
cors.Headers("X-Shared-Secret")
cors.Methods("GET", "POST")
})
})
Common plugin use cases:
- Protocol support (CORS, etc.)
- Additional documentation formats
- Custom validation rules
- Cross-cutting concerns (logging, metrics)
- Configuration file generation
See Also
- DSL Reference — Complete DSL reference for design files
- HTTP Guide — HTTP transport features and customization
- gRPC Guide — gRPC transport features and Protocol Buffers
- Quickstart — Getting started with code generation