Skip to content

Latest commit

 

History

History
205 lines (177 loc) · 9.77 KB

go-fiber.md

File metadata and controls

205 lines (177 loc) · 9.77 KB

Project Overview

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.

Code Style

Naming Conventions

  • 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: userID not uid, emailAddress not email
  • Constants: SCREAMING_SNAKE_CASE for package-level constants only
  • Interface names: end with er suffix. Examples: Reader, Writer, UserRepository
  • Receiver names: single letter matching type initial. Example: func (u *User) Validate()

Error Handling

  • Never use _ to ignore errors. Every error return must be explicitly handled
  • All error variables must be named err, never e or error
  • Error checks must immediately follow the function call: if err != nil { return err }
  • Wrap errors with context using fmt.Errorf("operation: %w", err) — never fmt.Errorf("operation: %v", err)
  • Custom error types must embed error interface and be defined in errors.go file in each package
  • HTTP handlers must convert errors to responses using a dedicated error mapper function, never inline status codes

Code Organization

  • 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

File Structure

Directory Layout

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

File Naming Rules

  • 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.go in same directory as source
  • Error files: errors.go in each package that defines custom errors
  • Model files: {domain}.go for domain models. Example: user.go

Package Organization

  • internal/handler: HTTP request handlers only. No business logic
  • internal/service: Business logic and orchestration. No database queries
  • internal/repository: Database access layer using sqlc queries
  • internal/model: Domain models and data structures
  • internal/middleware: Fiber middleware functions
  • internal/config: Configuration loading and validation
  • cmd/server: Application entry point only

Patterns

Dependency Injection

  • 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.go before starting server

Context Propagation

  • Every function that performs I/O must accept context.Context as 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, not context.WithValue()

Handler Structure

  • 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) or c.SendStatus(code)
  • Never write SQL queries or business logic in handlers

Service Layer

  • Service methods must accept context.Context as 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 Layer

  • Repository methods must accept context.Context as 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.ErrNoRows as-is, never wrap it
  • Repository must not perform business logic validation

Error Handling Pattern

  • 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 Pattern

  • 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

Testing Pattern

  • 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.go file in test directory
  • Integration tests in test/integration/ directory with _test.go suffix

Testing

Unit Testing Requirements

  • 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.go in same directory as source
  • Test function naming: Test{FunctionName}_{Scenario}. Example: TestGetUser_NotFound

Table-Driven Tests

  • 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

Mock Requirements

  • Create mocks.go in 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/assert for assertions, never if statements

Integration Testing

  • 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

Coverage Requirements

  • Minimum 80% code coverage for service layer
  • Minimum 70% code coverage for repository layer
  • Minimum 60% code coverage for handlers
  • Coverage excludes main.go and generated sqlc code
  • Run coverage with: go test -cover ./...

Test Isolation

  • 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

Error Testing

  • 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.ErrNoRows is 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>