Skip to content

Project Structure

goctl generates a consistent directory layout for every project. Understanding it is the fastest way to be productive in any go-zero codebase.

Generated by goctl api go -api user.api -dir .:

user-api/
├── etc/
│ └── user-api.yaml # Runtime config (host, port, DB, RPC endpoints)
├── internal/
│ ├── config/
│ │ └── config.go # Typed config struct that maps to the YAML
│ ├── handler/
│ │ ├── routes.go # Auto-generated route registration
│ │ └── loginhandler.go # One file per @handler — binds request, calls logic
│ ├── logic/
│ │ └── loginlogic.go # Business logic — the only file you normally edit
│ ├── svc/
│ │ └── servicecontext.go # Shared deps: DB pool, RPC clients, Redis, etc.
│ └── types/
│ └── types.go # Auto-generated request/response structs
└── user-api.go # main() — starts rest.Server

Generated by goctl rpc protoc user.proto --zrpc_out=.:

user-rpc/
├── etc/
│ └── user-rpc.yaml # ListenOn, Etcd registration, DB config
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── logic/
│ │ └── getuserlogic.go # One file per RPC method
│ ├── server/
│ │ └── userserver.go # gRPC server impl — delegates to logic layer
│ └── svc/
│ └── servicecontext.go
├── pb/
│ └── user/ # protoc-generated .pb.go and _grpc.pb.go
├── userclient/
│ └── user.go # go-zero generated type-safe client wrapper
└── user.go # main() — starts zrpc.Server
LayerPackageResponsibilityContains business logic?
Handlerinternal/handlerParse & validate HTTP request, call logic, write responseNo
Logicinternal/logicImplement use-case; orchestrate DB/cache/RPC callsYes
ServiceContextinternal/svcConstruct and hold shared dependencies once at startupNo
Configinternal/configMap YAML fields to typed Go structsNo
Modelinternal/modelData access layer (generated by goctl model)No

For repositories with multiple services, the standard convention is:

project-root/
├── service/
│ ├── user/
│ │ ├── api/ # user-api
│ │ └── rpc/ # user-rpc
│ ├── order/
│ │ ├── api/
│ │ └── rpc/
│ └── payment/
│ └── rpc/
├── common/ # Shared utilities (errors, middleware, etc.)
└── deploy/
├── docker-compose.yaml
└── k8s/
  • Thin handlers — a handler should do nothing except decode the request, call one logic method, and encode the response.
  • One logic file per use caseCreateOrderLogic, GetOrderLogic, CancelOrderLogic are separate files, even if the methods are small.
  • ServiceContext is the only constructor — never call sql.Open or redis.NewClient outside svc.NewServiceContext.
  • Config over code — every tunable value (timeouts, feature flags, downstream addresses) belongs in etc/*.yaml, not hardcoded.
  • Model layer is generated, logic layer is yours — regenerate models freely; the logic layer is where your business code lives and is never overwritten by goctl.