This is a Go backend service using Fiber web framework, PostgreSQL database, and sqlc for type-safe SQL generation. All code must prioritize explicit error handling, clear separation of concerns, and testability. AI assistants must never generate code that silently ignores errors, uses global state, or skips context propagation.
- Package names: lowercase, single word, no underscores. Examples:
handler,service,repository,config - File names: lowercase with underscores for multi-word files. Examples:
user_service.go,auth_handler.go - Function names: PascalCase for exported, camelCase for unexported
- Variable names: short names acceptable only in loops (i, j, k, err). All other variables must be descriptive:
userIDnotuid,emailAddressnotemail - Constants: SCREAMING_SNAKE_CASE for package-level constants only
- Interface names: end with
ersuffix. Examples:Reader,Writer,UserRepository - Receiver names: single letter matching type initial. Example:
func (u *User) Validate()
- Never use
_to ignore errors. Every error return must be explicitly handled - All error variables must be named
err, nevereorerror - Error checks must immediately follow the function call:
if err != nil { return err } - Wrap errors with context using
fmt.Errorf("operation: %w", err)— neverfmt.Errorf("operation: %v", err) - Custom error types must embed
errorinterface and be defined inerrors.gofile in each package - HTTP handlers must convert errors to responses using a dedicated error mapper function, never inline status codes
- Maximum function length: 30 lines. Longer functions must be split into helper functions
- Maximum file length: 300 lines. Larger files must be split by responsibility
- No blank lines between related statements. Use blank lines only to separate logical sections
- Imports must be grouped: stdlib, external packages, internal packages. Each group separated by blank line
- All exported functions must have doc comments starting with function name:
// GetUser retrieves a user by ID
project-root/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── handler/
│ │ ├── user_handler.go
│ │ ├── auth_handler.go
│ │ └── error.go
│ ├── service/
│ │ ├── user_service.go
│ │ ├── auth_service.go
│ │ └── errors.go
│ ├── repository/
│ │ ├── user_repository.go
│ │ └── errors.go
│ ├── db/
│ │ ├── queries.sql
│ │ └── schema.sql
│ ├── middleware/
│ │ ├── auth.go
│ │ └── logging.go
│ └── model/
│ ├── user.go
│ └── domain.go
├── migrations/
│ ├── 001_initial_schema.sql
│ └── 002_add_indexes.sql
├── test/
│ ├── fixtures/
│ │ └── users.json
│ └── integration/
│ └── user_test.go
├── go.mod
├── go.sum
└── CLAUDE.md
- Handler files:
{resource}_handler.go. Example:user_handler.go - Service files:
{resource}_service.go. Example:user_service.go - Repository files:
{resource}_repository.go. Example:user_repository.go - Test files:
{source_file}_test.goin same directory as source - Error files:
errors.goin each package that defines custom errors - Model files:
{domain}.gofor domain models. Example:user.go
internal/handler: HTTP request handlers only. No business logicinternal/service: Business logic and orchestration. No database queriesinternal/repository: Database access layer using sqlc queriesinternal/model: Domain models and data structuresinternal/middleware: Fiber middleware functionsinternal/config: Configuration loading and validationcmd/server: Application entry point only
- All services must accept dependencies as constructor parameters
- Constructor signature:
func New{Service}(repo {Repository}, logger Logger) *{Service} - Store dependencies as unexported fields:
type UserService struct { repo UserRepository } - Never use global variables for services, repositories, or loggers
- Wire dependencies in
main.gobefore starting server
- Every function that performs I/O must accept
context.Contextas first parameter - Pass context through all function calls:
s.repo.GetUser(ctx, userID) - Use
context.WithTimeout(ctx, 5*time.Second)for database operations - Never create context with
context.Background()inside handlers — use request context - Middleware must attach values to context using
ctx.Locals()in Fiber, notcontext.WithValue()
- Handler signature:
func (h *UserHandler) GetUser(c *fiber.Ctx) error - Extract and validate input at handler boundary using dedicated validation functions
- Call service layer for business logic:
user, err := h.service.GetUser(c.Context(), userID) - Convert service response to HTTP response using response mapper
- Return Fiber error using
c.Status(code).JSON(response)orc.SendStatus(code) - Never write SQL queries or business logic in handlers
- Service methods must accept
context.Contextas first parameter after receiver - Service methods return domain models, never database models
- Service methods must validate input: check for empty strings, invalid IDs, etc.
- Service methods must call repository methods, never execute queries directly
- Service methods must wrap errors with operation context:
fmt.Errorf("get user: %w", err)
- Repository methods must accept
context.Contextas first parameter after receiver - Repository methods must use sqlc-generated queries from
db/queries.sql - Repository methods must convert sqlc models to domain models before returning
- Repository methods must return
sql.ErrNoRowsas-is, never wrap it - Repository must not perform business logic validation
- Define custom error types in
errors.go:type NotFoundError struct { Resource string } - Implement
Error()string method on custom error types - Use
errors.Is()to check error types in handlers, never type assertions - Map errors to HTTP status codes in dedicated error handler function
- Handler error mapper:
func mapError(err error) (int, interface{}) { ... }
- Middleware must be applied in order: logging → auth → validation
- Auth middleware must attach user to context:
c.Locals("user", user) - Auth middleware must return
c.SendStatus(fiber.StatusUnauthorized)on failure - Logging middleware must use structured logging with request ID
- Middleware must not perform business logic
- Test files in same package as source code, not separate test package
- Table-driven tests required for all functions with multiple code paths
- Test table structure:
tests := []struct { name string; input X; expected Y; err bool } - Mock interfaces must be defined in
mocks.gofile in test directory - Integration tests in
test/integration/directory with_test.gosuffix
- Every service method must have unit tests
- Every repository method must have unit tests
- Every handler must have unit tests with mocked service layer
- Test file location:
{source_file}_test.goin same directory as source - Test function naming:
Test{FunctionName}_{Scenario}. Example:TestGetUser_NotFound
- All functions with conditional logic must use table-driven tests
- Table structure:
tests := []struct { name string; input InputType; expected OutputType; wantErr bool } - Loop through table:
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ... }) - Never use separate test functions for different scenarios
- Create
mocks.goin test directory for all interface mocks - Mock must implement full interface, never partial implementations
- Mock must track call count:
mock.CallCount int - Mock must store arguments for assertion:
mock.LastArg interface{} - Use
github.com/stretchr/testify/assertfor assertions, neverifstatements
- Integration tests in
test/integration/directory - Integration tests must use real PostgreSQL test database
- Integration tests must clean up data after each test using
t.Cleanup() - Integration tests must use fixtures from
test/fixtures/directory - Integration tests must test full request/response cycle through handlers
- Minimum 80% code coverage for service layer
- Minimum 70% code coverage for repository layer
- Minimum 60% code coverage for handlers
- Coverage excludes
main.goand generated sqlc code - Run coverage with:
go test -cover ./...
- Each test must be independent and runnable in any order
- Use
t.Parallel()for tests that don't share state - Never use global test fixtures or setup functions
- Use
t.Cleanup()to tear down test resources - Mock all external dependencies, never use real services in unit tests
- Test both success and error paths for every function
- Test specific error types using
errors.Is(), not string matching - Test error messages contain operation context
- Test that errors are properly wrapped with
fmt.Errorf - Test that
sql.ErrNoRowsis converted to domain error type
Source: Codelibrium — the marketplace for AI behaviour files. Browse multiple rulesets at codelibrium.com or install via CLI:
npx codelibrium-cli install <ruleset-name>