A production-ready Go web application built on Echo, featuring clean architecture, dependency injection (Uber FX), Keycloak auth integration, Redis caching, PostgreSQL, structured logging, and observability (OpenTelemetry + New Relic + Sentry).
- Features
- Project Structure
- Agent / Cursor
- Quick Start
- API Endpoints
- Example API Usage
- Development
- Architecture
- Error Handling System
- Database Connection Management
- Configuration
- Docker
- Database Monitoring and Troubleshooting
- Database Migrations
- Production Deployment
- Contributing
- License
- Clean Architecture: Layered modules and clear separation of concerns
- Dependency Injection: Uber FX for modular dependency management
- DTO & Model Layers: Separation between API DTOs and domain models
- Comprehensive Error Handling: Structured error system with context, logging, and monitoring
- Authentication: JWT-based authentication with Keycloak integration
- Caching: Redis cache provider
- Database: PostgreSQL with migrations (Atlas)
- Email: AWS SES integration
- Logging: Structured logging with Zap
- Observability: OpenTelemetry (OTLP traces/metrics/logs) + New Relic APM + Sentry error tracking
- Docker: Dockerfile and Compose services for Postgres/Redis
- Middleware: Auth, CORS, logging, rate limiting, error handling
- Health Checks: Built-in health endpoint
golang-boilerplate/
├─ cmd/
│ ├─ migrations/
│ │ └─ sql/ # Atlas migration files + atlas.sum
│ │ └─ 20260328081444_init_tables.sql
│ └─ server/
│ ├─ main.go # Application entrypoint + FX wiring
│ └─ routes/
│ └─ router.go # Echo routes and middleware
│
├─ docs/ # Project documentation (markdown)
│
├─ internal/
│ ├─ cache/ # Cache abstraction + Redis
│ │ ├─ cache.go
│ │ └─ redis.go
│ ├─ config/ # Config loader and env bindings
│ │ └─ config.go
│ ├─ constants/ # Error codes, pagination, providers
│ │ ├─ error_codes.go
│ │ ├─ pagination.go
│ │ └─ third_party_provider.go
│ ├─ db/ # Database connection management
│ │ ├─ manager.go # Database manager with connection pooling
│ │ └─ postgres.go # Postgres connection wrapper
│ ├─ dtos/ # API DTOs
│ │ ├─ common.go
│ │ ├─ company.go
│ │ ├─ email.go
│ │ ├─ health.go
│ │ └─ user.go
│ ├─ errors/ # Comprehensive error handling system
│ │ ├─ app_error.go # Custom error types and structures
│ │ ├─ handler.go # Error handler utilities
│ │ └─ middleware.go # Error middleware for panic recovery
│ ├─ handlers/ # Echo handlers
│ │ ├─ base.go # Base handler with error handling
│ │ ├─ company.go # Company management endpoints
│ │ ├─ health.go # Health check endpoints
│ │ └─ user.go # User management endpoints
│ ├─ httpclient/ # Outbound HTTP client (Resty)
│ │ └─ resty.go
│ ├─ integration/ # External integrations
│ │ ├─ auth/
│ │ │ ├─ auth.go
│ │ │ └─ keycloak.go
│ │ └─ email/
│ │ ├─ email.go
│ │ └─ ses.go
│ ├─ logger/
│ │ └─ logger.go
│ ├─ middlewares/
│ │ ├─ auth.go
│ │ ├─ basic_auth.go
│ │ ├─ cors.go
│ │ ├─ logging.go
│ │ └─ rate_limiter.go
│ ├─ models/
│ │ ├─ auth.go
│ │ ├─ base.go
│ │ ├─ company.go
│ │ ├─ email.go
│ │ └─ user.go
│ ├─ monitoring/
│ │ ├─ newrelic_zap.go
│ │ ├─ new_relic.go
│ │ └─ sentry.go
│ ├─ repositories/
│ │ ├─ abstract.go
│ │ ├─ company.go
│ │ └─ user.go
│ ├─ services/
│ │ ├─ auth.go
│ │ ├─ company.go
│ │ ├─ email.go
│ │ └─ user.go
│ └─ utils/
│ ├─ accent.go
│ ├─ date.go
│ └─ i18n/
│ └─ translator.go
│
├─ .cursor/ # Cursor rules, agents, commands, skills
├─ AGENTS.md # Agent onboarding (layers, make targets, Cursor index)
├─ atlas.hcl # Atlas env (GORM schema → migrate diff)
├─ Dockerfile
├─ docker-compose.yml
├─ go.mod
├─ go.sum
├─ Makefile
└─ README.md
For AI-assisted development in Cursor, start with AGENTS.md — layer map, HTTP surface, key patterns, and an index of .cursor/ rules, agents, skills, and slash commands (gb:plan, gb:cook, gb:fix, gb:test, gb:review-code).
Optional local MCP: copy .cursor/mcp.json.example to .cursor/mcp.json (gitignored).
- Go 1.25+
- Docker and Docker Compose
- Make (optional)
# Database migrations — install the Atlas CLI (see https://atlasgo.io/getting-started#installation)
# macOS (Homebrew):
brew install ariga/tap/atlas
# Or use the install script from the Atlas docs for Linux/other platforms.Docker is required for some Atlas commands (for example migrate-down and migrate-generate), which use a temporary dev database (DB_DEV_URL defaults to docker://postgres/18/dev in the Makefile).
git clone <repository-url>
cd golang-boilerplate
go mod tidycp cmd/server/.env.example cmd/server/.envMigration Make targets read PostgreSQL settings from cmd/server/.env (see Makefile: POSTGRES_* are composed into DB_DSN for Atlas).
make container-up# Run DB migrations (uses DB settings from cmd/server/.env)
make migrate-up
# Start the server
make upGET /api/v1/- Health check
GET /api/v1/health/database- Database health status with connection metricsGET /api/v1/health/metrics- Comprehensive database metrics and configuration
User Management:
POST /api/v1/users- Create userGET /api/v1/users/{id}- Get user by IDPUT /api/v1/users/{id}- Update userDELETE /api/v1/users/{id}- Delete userGET /api/v1/users- Get users listGET /api/v1/users/test-rest-client- Demo endpoint to test outbound REST client
Company Management:
POST /api/v1/companies- Create new companyGET /api/v1/companies/{id}- Get company by IDPUT /api/v1/companies/{id}- Update companyDELETE /api/v1/companies/{id}- Delete companyGET /api/v1/companies- Get companies list
curl -X POST http://localhost:3000/api/v1/users \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "john@example.com"}'curl -X GET http://localhost:3000/api/v1/users/123 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"curl -X POST http://localhost:3000/api/v1/companies \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corp", "description": "A great company"}'curl -X GET http://localhost:3000/api/v1/health/databasecurl -X GET http://localhost:3000/api/v1/health/metrics# Development
make container-up # Start Docker services (postgres, redis)
make container-down # Stop Docker services
make up # Run the server (cmd/server)
make build cmd=server service_name=main # Build linux binary for server
make dep # go mod tidy
make lint # Run golangci-lint
make format # Format code
# Testing (see Testing section for details)
make tests # Run all tests with coverage and race detection
make test-services # Run service layer tests
make test-utils # Run utility tests
make test-handlers # Run handler tests
make test-repositories # Run repository tests
make test-coverage # Run tests with coverage
make test-coverage-html # Generate HTML coverage report
make test-race # Run tests with race detection
make test-verbose # Run tests with verbose output
make test-specific TEST=TestName # Run a specific test
# Migrations (Atlas; DB URL from cmd/server/.env via Makefile DB_DSN)
make migrate-status
make migrate-up
make migrate-up-preview # dry-run apply
make migrate-down # requires Docker dev URL by default
make migrate-down-preview
make migrate-create name=add_table
make migrate-hash # refresh atlas.sum after editing migrations
make migrate-inspect # inspect live schema
make migrate-generate name=my_change # GORM diff → new migration (atlas.hcl env "gorm")
This project follows Go testing conventions and best practices for organizing and writing unit tests.
Test files are placed in the same directory as the source code they test, with the _test.go suffix:
internal/
├── services/
│ ├── user.go # Source code
│ └── user_test.go # Unit tests for user service
├── handlers/
│ ├── user.go
│ └── user_test.go # Unit tests for user handler
├── repositories/
│ ├── user.go
│ └── user_test.go # Unit tests for user repository
└── utils/
├── date.go
└── date_test.go # Unit tests for date utilities
The project provides several Makefile targets for running tests:
Main Test Commands:
# Run all tests with coverage and race detection (recommended)
make tests
# Run all tests with verbose output
make test-verbose
# Run all tests with coverage report
make test-coverage
# Generate HTML coverage report
make test-coverage-html
# Run tests with race detection
make test-raceLayer-Specific Test Commands:
# Run service layer tests
make test-services
# Run utility tests
make test-utils
# Run handler tests
make test-handlers
# Run repository tests
make test-repositoriesSpecific Test Commands:
# Run a specific test function
make test-specific TEST=TestUserService_Create
# Run a specific test with verbose output
make test-specific-verbose TEST=TestUserService_Create
# Run a specific test with coverage
make test-specific-coverage TEST=TestUserService_CreateDirect Go Commands (alternative to Makefile):
# Run tests for a specific package
go test ./internal/services
# Run tests for a specific package with coverage
go test -cover ./internal/services
# Run a specific test function
go test -run TestUserService_Create ./internal/services
# Generate HTML coverage report manually
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.htmlTests follow a table-driven approach for multiple scenarios:
func TestUserService_Create(t *testing.T) {
tests := []struct {
name string
req *dtos.CreateUserRequest
expectedError bool
}{
{
name: "success - valid request",
req: &dtos.CreateUserRequest{...},
expectedError: false,
},
{
name: "error - invalid email",
req: &dtos.CreateUserRequest{...},
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}Same Package (White-box Testing):
- Uses the same package name as source code (e.g.,
package services) - Can test unexported functions and internal implementation
- Best for unit tests that need access to internals
Test Package (Black-box Testing):
- Uses
_testsuffix (e.g.,package services_test) - Tests only the public API
- More resilient to internal refactoring
Repository Tests (internal/repositories/*_test.go):
- Test data access logic and database queries
- Mock or use in-memory databases
- Test CRUD operations, filters, sorting, pagination
Service Tests (internal/services/*_test.go):
- Test business logic and validation rules
- Mock repository interfaces
- Test error handling and orchestration
Handler Tests (internal/handlers/*_test.go):
- Test HTTP request/response handling
- Use Echo test utilities
- Mock service interfaces
- Test authentication, validation, status codes
Utility Tests (internal/utils/*_test.go):
- Test pure functions and helper utilities
- Usually no mocking needed
- Focus on edge cases and correctness
The project includes comprehensive test files demonstrating best practices:
Service Layer Tests:
internal/services/user_test.go- User service with mocked repositoriesinternal/services/company_test.go- Company service testsinternal/services/email_test.go- Email service with mocked email senderinternal/services/auth_test.go- Auth service with mocked auth provider
Utility Tests:
internal/utils/date_test.go- Date parsing and validation testsinternal/utils/sort_test.go- Sort validation with table-driven tests
HTTP Client Tests:
internal/httpclient/resty_test.go- REST client integration tests
The project uses testify for assertions and mocking:
assert- Better assertions with helpful error messagesrequire- Same as assert but stops test execution on failuremock- Mock objects for dependencies
- Keep tests isolated - Each test should be independent
- Test edge cases - Don't just test the happy path
- Use meaningful names - Test names should describe what is being tested
- Mock external dependencies - Use interfaces to mock databases, APIs, etc.
- Keep tests fast - Unit tests should complete quickly
- Test error cases - Test both success and failure scenarios
- Use table-driven tests - For multiple test cases with similar structure
- Avoid testing third-party code - Focus on your own code
Aim for good test coverage, especially for critical business logic:
# Check coverage for all packages
go test -cover ./...
# Generate detailed coverage report
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
# View coverage in browser
go tool cover -html=coverage.outTests are automatically run in CI/CD pipelines. Ensure all tests pass before committing:
# Run full test suite before committing (recommended)
make tests
# Or run tests with race detection separately
make test-race
# Check test coverage
make test-coverage-htmlPre-commit Checklist:
- ✅ All tests pass:
make tests - ✅ Code is formatted:
make format - ✅ Linter passes:
make lint - ✅ Coverage meets requirements:
make test-coverage
Uber FX wires the application graph (config, logger, monitoring, db, cache, repositories, services, handlers, HTTP server). See cmd/server/main.go for providers and lifecycle hooks.
The application separates domain models and API DTOs:
- Models (
internal/models/): Domain entities - DTOs (
internal/dtos/): Request/response structs
Benefits:
- Type Safety and stability across API boundaries
- Security: sensitive fields are not exposed via DTOs
- Consistency: standardized response envelope
The application features a comprehensive error handling system that provides consistent error responses, structured logging, and monitoring integration.
The system defines several error types for different scenarios:
ErrorTypeValidation- Input validation errorsErrorTypeNotFound- Resource not found errorsErrorTypeUnauthorized- Authentication errorsErrorTypeForbidden- Authorization errorsErrorTypeConflict- Resource conflict errorsErrorTypeInternal- Internal server errorsErrorTypeExternal- External service errorsErrorTypeDatabase- Database errorsErrorTypeCache- Cache errorsErrorTypeTimeout- Timeout errors
All errors follow a consistent structure with rich context:
type AppError struct {
Code string // Error code from constants
Message string // Human-readable error message
Type ErrorType // Error category
HTTPStatus int // HTTP status code
Cause error // Underlying error
Context map[string]interface{} // Additional context
Timestamp time.Time // When error occurred
StackTrace string // Stack trace for debugging
Operation string // Operation being performed
Resource string // Resource being accessed
}// Simple error creation
err := errors.ValidationError("Invalid email format", nil)
// Error with context
err := errors.DatabaseError("Failed to create user", dbErr).
WithOperation("create_user").
WithResource("user").
WithContext("user_id", userID).
WithContext("email", email)func (h *UserHandler) CreateUser(c echo.Context) error {
// Validation errors
if err := h.validator.Struct(requestDto); err != nil {
return h.HandleError(c, errors.ValidationError("Validation failed", err))
}
// Service errors
user, err := h.userService.Create(c.Request().Context(), &requestDto)
if err != nil {
return h.HandleError(c, err) // Error is already wrapped in service
}
return h.SuccessResponse(c, "User created successfully", user, nil)
}All errors are returned in a consistent format:
{
"meta": {
"error_code": "VALIDATION_ERROR",
"message": "Invalid email format",
"code": 400
},
"data": null
}The error handling system includes middleware for:
- Panic Recovery - Catches panics and converts them to structured errors
- Centralized Error Handling - Processes all errors and returns consistent responses
- Structured Logging: All errors are logged with context fields
- Sentry Integration: Errors are automatically reported to Sentry with context
- OpenTelemetry Integration: Traces, metrics, and error logs exported over OTLP — see OpenTelemetry Guide
- Stack Traces: Internal errors include stack traces for debugging
The application features a comprehensive database connection management system that provides enterprise-grade reliability, monitoring, and performance.
- Advanced Connection Pooling with configurable parameters
- Health Monitoring with automatic checks and retry logic
- Graceful Shutdown handling
- Connection Metrics and monitoring
- Error Handling with structured error reporting
- Automatic Reconnection on failures
The system consists of:
- DatabaseManager (
internal/db/manager.go) - Core connection management, health monitoring, metrics collection, and retry logic - PostgresDB (
internal/db/postgres.go) - Wrapper around GORM with integration to DatabaseManager - Configuration (
internal/config/config.go) - Database connection parameters, pool settings, and timeout configurations
The system implements advanced connection pooling with:
- Configurable Pool Size: Set maximum open and idle connections
- Connection Lifecycle Management: Automatic cleanup of old connections
- Idle Connection Management: Efficient handling of unused connections
// Example usage
db := &db.PostgresDB{}
err := db.NewPostgresDB(cfg)
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// Get connection metrics
metrics := db.GetMetrics()
log.Printf("Open connections: %d", metrics.OpenConnections)Automatic health checks every 30 seconds:
- Connection Validation: Ping database to verify connectivity
- Response Time Tracking: Monitor query response times
- Error Tracking: Log and report connection issues
- Retry Logic: Automatic reconnection on failures
// Manual health check
healthStatus := db.HealthCheck()
if !healthStatus.IsHealthy {
log.Errorf("Database unhealthy: %s", healthStatus.LastError)
}Real-time monitoring of connection statistics:
- Pool Statistics: Open, idle, and in-use connections
- Wait Metrics: Connection wait times and counts
- Configuration: Current pool settings
Set via .env (loaded by viper and godotenv):
- Server:
APP_ENV,APP_NAME,APP_VERSION,TIMEZONE,APP_HTTP_SERVER(e.g.:3000) - Database:
POSTGRES_HOST,POSTGRES_PORT,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB,DATABASE_DEBUG - Database Connection Pool:
DATABASE_MAX_OPEN_CONNS(default: 25),DATABASE_MAX_IDLE_CONNS(default: 5),DATABASE_CONN_MAX_LIFETIME(default: 5m),DATABASE_CONN_MAX_IDLE_TIME(default: 1m) - Database Timeouts:
DATABASE_CONNECT_TIMEOUT(default: 30s),DATABASE_QUERY_TIMEOUT(default: 30s) - Database Retry:
DATABASE_RETRY_ATTEMPTS(default: 3),DATABASE_RETRY_DELAY(default: 1s) - Database Health:
DATABASE_HEALTH_TIMEOUT(default: 5s) - Database SSL:
DATABASE_SSL_MODE(default: disable),DATABASE_TIMEZONE(default: UTC) - Cache:
CACHE_PROVIDER(default: redis),REDIS_HOST,REDIS_PORT,REDIS_PASSWORD,REDIS_DB,REDIS_POOL_SIZE,REDIS_DIAL_TIMEOUT,REDIS_READ_TIMEOUT,REDIS_WRITE_TIMEOUT,REDIS_POOL_TIMEOUT,REDIS_MAX_RETRIES,REDIS_MIN_RETRY_BACKOFF,REDIS_MAX_RETRY_BACKOFF - Authentication:
AUTH_PROVIDER,KEYCLOAK_URL,KEYCLOAK_REALM,KEYCLOAK_CLIENT_ID,KEYCLOAK_CLIENT_SECRET,KEY_CLAIMS,KEYCLOAK_REDIRECT_URI - Email:
EMAIL_PROVIDER(ses),AWS_SES_REGION,AWS_SES_ACCESS_KEY,AWS_SES_SECRET_KEY - Rate Limiting:
DEFAULT_RATE_LIMIT,AUTH_RATE_LIMIT,PUBLIC_RATE_LIMIT,RATE_LIMIT,RATE_LIMIT_DURATION - Observability:
NEWRELIC_APP_NAME,NEWRELIC_LICENSE,SENTRY_DSN,OTEL_*— see OpenTelemetry Guide
| Parameter | Default | Description |
|---|---|---|
DATABASE_MAX_OPEN_CONNS |
25 | Maximum number of open connections |
DATABASE_MAX_IDLE_CONNS |
5 | Maximum number of idle connections |
DATABASE_CONN_MAX_LIFETIME |
5m | Maximum lifetime of a connection |
DATABASE_CONN_MAX_IDLE_TIME |
1m | Maximum idle time of a connection |
DATABASE_CONNECT_TIMEOUT |
30s | Connection timeout |
DATABASE_QUERY_TIMEOUT |
30s | Query timeout |
DATABASE_HEALTH_TIMEOUT |
5s | Health check timeout |
DATABASE_RETRY_ATTEMPTS |
3 | Number of retry attempts |
DATABASE_RETRY_DELAY |
1s | Delay between retries |
Default limits are configurable via env. Middleware is applied globally in router.go.
# Build image
docker build -t golang-boilerplate .
# Run container (ensure APP_HTTP_SERVER is set to :3000 in container env)
docker run -p 3000:3000 --env-file .env golang-boilerplate- PostgreSQL: Database (5432)
- Redis: Cache (6379)
- Jaeger: Trace UI (16686) — local OpenTelemetry trace backend
- OTel Collector: OTLP receiver (4317 gRPC, 4318 HTTP)
- App service is commented out in
docker-compose.yml. Run the app locally withmake upor create your own app service. - Start observability stack only:
make otel-up— see OpenTelemetry Guide
- App service is commented out in
-
Connection Pool Utilization
- Open connections vs. max connections
- Idle connections
- Wait times
-
Health Status
- Connection health
- Response times
- Error rates
-
Performance Metrics
- Query response times
- Connection establishment time
- Retry attempts
- Connection pool utilization > 80%
- Health check failures
- Response times > 1 second
- Retry attempts > 2
-
Connection Pool Exhaustion
- Increase
DATABASE_MAX_OPEN_CONNS - Check for connection leaks
- Monitor connection usage patterns
- Increase
-
Slow Queries
- Check
DATABASE_QUERY_TIMEOUT - Monitor query performance
- Optimize database queries
- Check
-
Connection Failures
- Check network connectivity
- Verify database credentials
- Monitor database server health
-
High Response Times
- Check connection pool settings
- Monitor database server performance
- Optimize query performance
-
Enable Debug Logging
DATABASE_DEBUG=true LOG_LEVEL=debug
-
Check Health Endpoints
curl http://localhost:3000/api/v1/health/database curl http://localhost:3000/api/v1/health/metrics
-
Monitor Logs
- Check application logs for database errors
- Monitor Sentry for error reports
- Use database monitoring tools
- Small Applications: 5-10 connections
- Medium Applications: 10-25 connections
- Large Applications: 25-50 connections
- High Traffic: 50+ connections
Migrations are managed with Atlas. SQL files live under cmd/migrations/sql/; the checksum file cmd/migrations/sql/atlas.sum must stay in sync—run make migrate-hash after you add or edit migration files.
Configuration:
atlas.hcl— defines thegormenv: loads schema frominternal/modelsvia atlas-provider-gorm, writes diffs intocmd/migrations/sql, and uses a dev database for planning.Makefile— setsMIGRATION_DIR(file://cmd/migrations/sql), buildsDB_DSNfromcmd/server/.env, and setsDB_DEV_URL(defaultdocker://postgres/18/dev) for commands that need a dev instance.
Common commands:
# Status and apply
make migrate-status
make migrate-up
make migrate-up-preview
# Roll back (needs a working Docker setup for the default dev URL)
make migrate-down
make migrate-down-preview
# New empty migration file
make migrate-create name=add_users
# Generate a migration from GORM models (run from repo root; set name=...)
make migrate-generate name=describe_your_change
# After hand-editing SQL migrations
make migrate-hashFor full Atlas CLI options, see the Atlas documentation.
- Build and push Docker image or deploy the binary built from
cmd/server. - Set
APP_ENV=productionand all required env vars. - Apply database migrations before or during rollout (for example
atlas migrate apply --dir "file://cmd/migrations/sql" --url "$DATABASE_URL", or your orchestrator’s equivalent). - Expose the port configured by
APP_HTTP_SERVER(e.g.:3000).
See CONTRIBUTING.md. If you use Cursor, also read AGENTS.md.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Run tests and linting
- Submit a pull request
This project is licensed under the MIT License.