Comprehensive coding rules for building high-performance, production-grade REST/JSON APIs with Go, Gin, and GORM.
You're already building APIs with Go and Gin. You know the patterns, understand the ecosystem, and can write clean handlers. But your development velocity hits friction points daily – jumping between documentation, fighting inconsistent error handling, and debugging performance issues that could have been prevented with the right architectural decisions upfront.
Configuration Chaos: Every new service becomes an exercise in reinventing project structure, error handling patterns, and database setup. You spend the first day of each project making the same architectural decisions.
Performance Blindspots: You're optimizing based on intuition rather than data, and those mysterious latency spikes only surface in production when it's too late to profile effectively.
Testing Inconsistency: Your test coverage varies wildly between features because you're making up testing patterns as you go, leading to brittle integration tests and missed edge cases.
Error Handling Drift: Each developer on your team handles errors differently, creating APIs that respond inconsistently and debugging sessions that waste hours tracking down swallowed errors.
These rules establish a complete architectural blueprint for Go + Gin development that eliminates decision fatigue and prevents common pitfalls before they happen. Instead of making the same structural choices repeatedly, you get:
Immediate Architecture Decisions: Project structure, error handling patterns, database connections, and testing strategies are predetermined and battle-tested. Your gin.Engine setup, middleware stack, and handler patterns are consistent across every project.
Built-in Performance Culture: Profiling endpoints, benchmark-driven optimization, and PGO integration become default practices rather than afterthoughts. You profile before optimizing, not after production incidents.
Bulletproof Error Handling: Every API returns consistent JSON envelopes with proper HTTP status mapping. Errors are wrapped with context, logged with structured data, and never leak internal details to clients.
Instead of spending hours setting up project structure, database connections, and middleware stacks, you get a complete, production-ready foundation:
// This entire router setup becomes automatic
func NewRouter(cfg *config.Config, deps *Dependencies) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(middleware.StructuredLogger())
r.Use(middleware.CORS())
api := r.Group("/api/v1")
api.Use(middleware.Auth())
// Feature groups mount automatically
return r
}
Every handler follows the same pattern, eliminating the mental overhead of error response formatting:
func (h *UserHandler) Create(c *gin.Context) {
var req dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.errs.BadRequest(c, err) // Consistent response + abort
return
}
user, err := h.svc.Create(c.Request.Context(), req)
if err != nil { h.errs.Handle(c, err); return }
c.JSON(http.StatusCreated, envelope.Data(user))
}
Profiling and optimization become part of your standard workflow, not emergency responses:
// pprof endpoints enabled with proper security
func (r *Router) registerDebugRoutes() {
debug := r.Group("/debug")
debug.Use(middleware.AdminAuth()) // Never expose publicly
debug.GET("/pprof/*any", gin.WrapH(http.DefaultServeMux))
}
Before: 45 minutes of setup, inconsistent patterns, manual testing After: 12 minutes with consistent structure and automatic test scaffolding
Generate Feature Structure (2 minutes)
features/user/
├── handler.go # HTTP transport layer
├── service.go # Business logic
├── repository.go # Data access
├── model.go # GORM models
└── errors.go # Domain-specific errors
Implement Handler (5 minutes)
// Pattern is identical across all features
type UserHandler struct {
svc UserService
errs *ErrorHandler
}
func (h *UserHandler) Routes(r *gin.RouterGroup) {
users := r.Group("/users")
users.POST("", h.Create)
users.GET("/:id", h.GetByID)
users.PUT("/:id", h.Update)
}
Write Tests (5 minutes)
// Table-driven tests with consistent setup
func TestUserHandler_Create(t *testing.T) {
tests := []struct {
name string
input dto.CreateUserRequest
mockSetup func(*mocks.UserService)
expectedStatus int
}{
// Test cases follow identical pattern
}
}
Before: Guesswork, production debugging, reactive optimization After: Data-driven decisions with built-in profiling
// Benchmark critical paths automatically
func BenchmarkUserService_Create(b *testing.B) {
svc := setupService(b)
req := generateTestRequest()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := svc.Create(context.Background(), req)
require.NoError(b, err)
}
}
Before: N+1 queries discovered in production After: Preloading patterns enforced by default
// GORM queries follow consistent optimization patterns
func (r *UserRepository) GetWithPosts(ctx context.Context, id uuid.UUID) (*User, error) {
var user User
err := r.db.WithContext(ctx).
Preload("Posts"). // Prevent N+1
Where("id = ?", id).
First(&user).Error
return &user, errors.WithStack(err) // Always wrap errors
}
Copy the Cursor Rules configuration into your .cursor-rules file in your project root.
mkdir your-api && cd your-api
go mod init github.com/yourorg/your-api
mkdir -p {cmd/server,internal/http,features/user,api/v1}
The rules guide you through setting up:
Follow the feature-oriented structure:
// features/user/handler.go - HTTP transport
// features/user/service.go - Business logic
// features/user/repository.go - Data access
// features/user/model.go - GORM models
// Enable pprof for performance monitoring
import _ "net/http/pprof"
// Add benchmark tests for critical paths
go test -bench=. -benchmem -cpuprofile=cpu.prof
Start building APIs that scale with your team's velocity. Your next Go + Gin project should take 15 minutes to bootstrap, not 45. Copy these rules and watch your development friction disappear.
You are an expert in Go, Gin, GORM, SQL, Docker, and modern cloud-native tooling.
---
Key Principles
- Prefer small, composable functions with explicit inputs/outputs.
- Keep business logic framework-agnostic; isolate HTTP concerns at handler layer.
- Fail fast: validate early, return precise errors, defer success logic until all guards pass.
- Constructor-based dependency injection (no global state). Use interfaces for mocking.
- Configuration is environment-specific and 12-Factor compliant (env vars > files > defaults).
- Write tests first for business/use-case layer; treat HTTP tests as thin integration checks.
- Profile before optimizing; measure with pprof/benchstat, optimise only proven hotspots.
- Keep codebase feature-oriented (modules) over technical layering.
---
Go Language Rules
- Module naming: use full import path (e.g. github.com/acme/payments) and `go 1.22`.
- File & test naming
• Production files: snake_case discouraged; use lowercase with optional underscores when splitting logical units (payment_service.go).
• Tests end with `_test.go`; test funcs start with `Test*`; table-driven style.
- Package organisation
• cmd/ → entrypoints (main)
• internal/ → private packages not meant for reuse
• pkg/ → reusable libs
• api/ → transport contracts (DTO, OpenAPI spec)
• features/<domain>/ → handler, service, repo, models
- Error handling
• Always return `(T, error)`; never panic in production paths.
• Wrap with `%w` (errors.Join / errors.Wrap) to preserve stack.
• Sentinel errors live in dedicated `errors.go` inside domain package.
- Concurrency
• Pass `context.Context` as first parameter when cancellation/timeouts needed.
• Never leak goroutines: select { case <-ctx.Done() }.
• Use `sync.Pool` only for hot paths validated by benchmark.
- Generics
• Use where it removes duplication (e.g., repository helper). Avoid premature abstraction.
- Comments
• Exported identifiers get godoc comments starting with identifier name.
---
Error Handling & Validation
- All handlers must return JSON envelope:
{
"error": { "code": "<string>", "message": "<human readable>" },
"data": <payload|null>
}
- HTTP status mapping
• 400 → validation / binding errors
• 401 → authentication failed
• 403 → authorisation failed
• 404 → resource not found
• 409 → conflict / duplicate
• 422 → semantic validation fail
• 500 → unexpected server error (never leak internals)
- Validation
• Use `binding:"required,uuid"` etc. in struct tags.
• Custom validators belong in `pkg/validator/` and registered during bootstrap.
- Middleware catches all returned errors, logs with structured logger, and formats JSON envelope.
---
Gin Framework Rules
- Router setup lives in `internal/http/router.go`; return `*gin.Engine` configured with:
• `gin.Recovery()` + custom `ErrorMiddleware` (structured)
• `gin.Logger()` or zap-based logger
• CORS, RateLimiter, Auth middlewares as needed
- Routing
• Group by resource (`/api/v1/users`) and mount version as first segment.
• Use HTTP verbs properly: GET list/one, POST create, PUT replace, PATCH update, DELETE remove.
- Handlers pattern
```go
func (h *UserHandler) Create(c *gin.Context) {
var req dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.errs.BadRequest(c, err) // aggregator writes response & aborts
return
}
user, err := h.svc.Create(c.Request.Context(), req)
if err != nil { h.errs.Handle(c, err); return }
c.JSON(http.StatusCreated, envelope.Data(user))
}
```
- Context
• Always derive new ctx from `c.Request.Context()` when calling services.
• Store auth claims or request-scoped values via keys declared in a `contextkey` package.
- Streaming / large bodies
• For >10MiB uploads, use `c.Request.Body` + io.Copy to tmp file.
- JSON rendering uses `c.JSON` (never `fmt.Fprintf`) to keep Gin optimisations.
---
GORM Database Rules
- Connection is initialised once in `infrastructure/db.go`; set connection pool limits (MaxOpen, MaxIdle, ConnMaxLifetime).
- Models
• Each model in its own file; use struct tags for constraints.
• Use `sql.Null*` or pointer fields for nullable columns.
- Transactions
• Wrap multi-step operations in `db.Transaction(func(tx *gorm.DB) error { ... })`.
• Never ignore returned error; propagate with `%w`.
- Query optimisation
• Use `Preload`/`Joins` to avoid N+1 verified via logs.
• Add explicit indexes with `gorm:"index:idx_user_email,unique"`.
---
Testing
- Unit tests
• Table-driven, use Testify `require` for fatal and `assert` for non-fatal checks.
• Mocks via interfaces + `github.com/stretchr/testify/mock`.
- HTTP tests
• Use `httptest.NewRecorder()` + `router.ServeHTTP`.
• Validate status codes, headers, and JSON body with `json.Unmarshal` to struct.
- Integration
• Spin up dockerised Postgres via Testcontainers; apply migrations before tests.
- Coverage goal >= 80% on business logic packages.
---
Performance & Profiling
- Enable pprof endpoints only under `debug` build tag or admin-auth route.
- Benchmark critical paths with `go test -bench=. -benchmem`.
- Apply PGO (`go build -pgo=prof.out`) in CI for release builds.
- Memory
• Reuse byte slices using `sync.Pool` for JSON marshalling if proven hotspot.
• Avoid reflection in tight loops; prefer type-safe code.
- Routing performance already O(k) via Gin's radix tree; keep static segments over params where possible.
---
Security Rules
- Use `golang.org/x/crypto/bcrypt` for password hashing (cost ≥ 12).
- CSRF protection required on browser-facing routes (double submit cookie strategy).
- Validate JWT signature & expiry in auth middleware; rotate keys via JWKs.
- Never log sensitive fields (`password`, `token`, `Authorization`). Use zap `zap.Field{}` with redaction.
- Run `gosec ./...` in CI; fix high severity issues.
---
CI/CD & Tooling
- go vet, staticcheck, revive run in lint stage.
- go test ‑race for PRs.
- gofumpt auto-format; reject unformatted code.
- Docker image built with multi-stage:
• builder (golang:1.22) → scratch/alpine minimal runtime.
- Semantic versioning tags & goreleaser for binaries.
---
Common Pitfalls & Guardrails
- Ignoring `ctx.Err()` after long operations → always check before heavy DB calls.
- Swallowing DB errors (`rows.Err`) → wrap and bubble up.
- Over-abstracting repositories early → start simple, refactor after duplication appears.
- Using global `*gorm.DB` in tests → pass per-test instance to avoid race.
---
Reference Example Repo Structure
```
├── api
│ ├── v1
│ │ └── openapi.yaml
├── cmd
│ └── server
│ └── main.go
├── config
│ └── config.yaml
├── internal
│ ├── http
│ │ ├── middleware
│ │ └── router.go
│ └── db
│ └── postgres.go
├── features
│ └── user
│ ├── handler.go
│ ├── service.go
│ ├── repository.go
│ ├── model.go
│ └── errors.go
├── migrations
└── tests
```
Adhere strictly to these rules to ensure clean, maintainable, and performant Go + Gin codebases.