| name | go-structure |
| description | Recommend and scaffold Go project layouts based on project type (CLI, API, library, monorepo). Internal skill used by golang-pro agent when initializing projects or reviewing structure. |
| user-invocable | false |
| allowed-tools | Read, Glob, Grep, Bash(go:*), Bash(mkdir:*) |
Go Project Structure
Recommend and scaffold idiomatic Go project layouts. Matches structure to project type and size โ start simple, grow when needed.
When to Use
- Initializing a new Go project or module
- User asks about project layout or directory organization
- Reviewing an existing project for structural anti-patterns
- Migrating from flat layout to a more structured one
- Setting up a monorepo with multiple services
Prerequisites
- go.mod exists or will be created: The project must be a Go module.
- Project type is known or inferrable: CLI tool, REST API, library, or monorepo.
Core Principle
Simplicity first, complexity when necessary. Do not impose structure upfront. Let it emerge from actual needs. A single main.go is a valid starting point.
Anti-Patterns to Flag
| Anti-Pattern | Problem | Fix |
|---|
Generic package names (utils, helpers, common, base) | Become dumping grounds, convey no intent | Rename to specific purpose: validator, auth, cache |
Over-nesting (internal/services/user/handlers/http/v1/) | Cognitive load, cumbersome imports | Flatten to internal/user/handler.go |
Circular dependencies (A imports B, B imports A) | Compilation error, poor separation | Extract shared types into a separate package or use interfaces |
| Business logic in HTTP handlers | Couples domain to transport, hard to test | Move to service layer, handlers only do HTTP concerns |
The main.go monster (all logic in one file) | Untestable, circular deps, impossible to maintain | Extract into packages; main.go only wires deps and starts |
| Shared database across microservices | Distributed monolith, schema changes break all services | Each service owns its data; communicate via APIs |
Using pkg/ for non-library code | Misleading signal, unnecessary indirection | Move to internal/ or project root |
Premature cmd/ for single-binary projects | Unnecessary nesting | Keep main.go at root until multiple binaries are needed |
| Hardcoded DB credentials / config values | Security risk, not deployable across environments | Use env vars or config system; never commit secrets |
Workflow: Recommend Structure
Step 1: Detect Project Type
Analyze the codebase to classify:
| Signal | Project Type |
|---|
Single main.go at root, flag/cobra imports | CLI tool |
net/http or framework imports (gin, chi, echo, fiber) | REST API service |
No main.go, only library packages | Reusable library |
Multiple main.go or go.work file | Monorepo / multi-binary |
go.mod only, empty or minimal | New project (ask user) |
Step 2: Recommend Layout
Based on project type and current size, recommend the appropriate layout from the reference patterns below.
Sizing rules:
- Small (<10 Go files): Flat layout, no directories needed
- Medium (10โ30 Go files): Introduce
internal/ with feature-based packages
- Large (30+ Go files or multiple binaries): Full
cmd/ + internal/ structure
Step 3: Report Findings
For existing projects, present a table of findings:
| Finding | Severity | Recommendation |
|---|
utils/ package detected | Warning | Rename to specific purpose |
Business logic in handler.go | Warning | Extract to service.go |
No internal/ for private code | Info | Consider internal/ as project grows |
Reference Layouts
Flat Layout (Small CLI / Tool)
For single-purpose tools and prototypes. Grow from here.
mytool/
โโโ main.go
โโโ go.mod
โโโ go.sum
โโโ README.md
CLI Application (Cobra/Viper)
mytool/
โโโ main.go
โโโ command/
โ โโโ root.go
โ โโโ version.go
โโโ go.mod
โโโ README.md
REST API Service
Organize by feature/domain, not by technical layer.
myapi/
โโโ cmd/
โ โโโ api/
โ โโโ main.go # Minimal: wire deps, load config, start
โโโ internal/
โ โโโ config/
โ โ โโโ config.go
โ โโโ middleware/
โ โ โโโ auth.go
โ โโโ user/ # Feature package
โ โ โโโ handler.go # HTTP concerns only
โ โ โโโ handler_test.go
โ โ โโโ service.go # Business logic
โ โ โโโ service_test.go
โ โ โโโ repository.go # Data access interface + implementation
โ โ โโโ repository_test.go
โ โโโ product/
โ โโโ handler.go
โ โโโ service.go
โ โโโ repository.go
โโโ go.mod
โโโ README.md
Key rule: cmd/*/main.go is minimal โ only dependency wiring, config loading, and server start. All logic lives in internal/.
Layer Responsibilities Inside a Feature Package
Each file in a feature package (internal/user/) has a clear, single responsibility:
| File | Layer | Responsibility | Imports |
|---|
handler.go | Transport | HTTP request/response only: parse input, call service, write response. No business logic. | net/http, service interface |
service.go | Business | Domain logic and orchestration. Validates rules, coordinates repositories. Pure Go โ no HTTP, no SQL. | Repository interface, domain types |
repository.go | Data access | Defines the Repository interface (port) AND its implementation. Talks to DB. | database/sql, domain types |
model.go | Domain | Entities, value objects, custom error types. No external dependencies. | Standard library only |
Dependency flow: Handler โ Service โ Repository (interfaces). Dependencies always point inward.
Dependency Injection Wiring
Services receive their dependencies as interfaces via constructors โ never create them internally:
type Repository interface {
FindByID(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
}
type Service struct {
repo Repository
}
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
func main() {
db := connectDB()
userRepo := user.NewPostgresRepository(db)
userService := user.NewService(userRepo)
userHandler := user.NewHandler(userService)
}
This keeps services testable (mock the interface) and swappable (change DB without touching business logic).
Hexagonal / DDD (Large Service)
For complex domain logic with clear port/adapter separation.
myservice/
โโโ cmd/
โ โโโ api/
โ โโโ main.go
โโโ internal/
โ โโโ domain/
โ โ โโโ user/
โ โ โโโ entity.go # Domain models, value objects
โ โ โโโ repository.go # Port (interface)
โ โ โโโ service.go # Domain services
โ โโโ application/
โ โ โโโ user/
โ โ โโโ create_user.go # Use case
โ โ โโโ get_user.go # Use case
โ โ โโโ service.go # Orchestration
โ โโโ adapter/
โ โโโ http/
โ โ โโโ user_handler.go # Inbound adapter
โ โโโ postgres/
โ โ โโโ user_repo.go # Outbound adapter (implements port)
โ โโโ redis/
โ โโโ cache.go # Outbound adapter
โโโ api/
โ โโโ openapi.yaml
โโโ go.mod
โโโ README.md
Dependency rule: Dependencies flow inward. Domain has zero external imports. Adapters implement domain interfaces.
Reusable Library
Only use pkg/ when code is intended for external import.
mylib/
โโโ mylib.go # Public API at package root
โโโ mylib_test.go
โโโ internal/
โ โโโ parser/ # Private implementation
โ โโโ parser.go
โ โโโ parser_test.go
โโโ go.mod
โโโ README.md
Monorepo (Multiple Services)
myproject/
โโโ cmd/
โ โโโ api/
โ โ โโโ main.go
โ โโโ worker/
โ โ โโโ main.go
โ โโโ scheduler/
โ โโโ main.go
โโโ internal/
โ โโโ shared/ # Shared internal packages
โ โโโ api/ # API-specific code
โ โโโ worker/ # Worker-specific code
โ โโโ scheduler/ # Scheduler-specific code
โโโ go.work # Go workspace file
โโโ go.mod
โโโ README.md
Workflow: Scaffold Structure
When creating a new project or restructuring:
Step 1: Confirm Project Type
Ask user to confirm the detected or intended project type if ambiguous.
Step 2: Create Directories
Create only the directories needed for the chosen layout. Do not create empty placeholder directories.
Step 3: Create Minimal Files
For new projects, create:
main.go with minimal bootstrap code (for binaries)
- Package files with package declaration and doc comment
Step 4: Validate
go build ./...
go vet ./...
Organizing by Feature vs Layer
Prefer feature-based organization:
# Good: organized by domain
internal/
โโโ user/
โ โโโ handler.go
โ โโโ service.go
โ โโโ repository.go
โโโ order/
โโโ handler.go
โโโ service.go
โโโ repository.go
# Avoid: organized by layer
internal/
โโโ handlers/
โ โโโ user.go
โ โโโ order.go
โโโ services/
โ โโโ user.go
โ โโโ order.go
โโโ repositories/
โโโ user.go
โโโ order.go
Feature-based packages are self-contained, reduce cross-package imports, and make it easy to understand a domain in one place.
Workflow: Review Layer Violations
When reviewing an existing project, check for these structural violations:
Step 1: Scan for Logic in Handlers
Grep handler files for signs of business logic leaking into the transport layer:
- Direct SQL queries (
db.Query, db.Exec) in handler files
- Complex conditionals or domain validation beyond input parsing
- Direct calls to external services (HTTP clients, message queues)
Step 2: Scan for Transport in Services
Check service files for HTTP or framework imports that don't belong:
net/http imports in service layer
- Framework-specific types (gin.Context, echo.Context) in service signatures
- JSON marshaling in service code
Step 3: Check Dependency Direction
Verify dependencies flow inward (handler โ service โ repository):
- Services must not import handler packages
- Repositories must not import service packages
- Domain types must not import infrastructure packages
Step 4: Report
| File | Violation | Severity | Fix |
|---|
user/handler.go | Contains db.Query call | Error | Move to repository |
order/service.go | Imports net/http | Warning | Accept domain types, not HTTP types |
auth/handler.go | 50+ lines of validation logic | Warning | Extract to service method |
Error Handling
| Condition | Action |
|---|
No go.mod found | Ask if user wants to run go mod init |
| Project type ambiguous | Ask user to confirm type before recommending |
| Circular dependency detected | Report the cycle and suggest extraction of shared types |
utils/ or helpers/ package found | Flag as anti-pattern, suggest specific name |