Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mnah05-boiler-go-21-79.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Prerequisites

Before you begin, ensure you have the following installed:
The project uses Docker Compose to run PostgreSQL and Redis, so you don’t need to install them separately.

Installation

1

Clone the repository

git clone https://github.com/mnah05/boiler-go.git
cd boiler-go
2

Set up environment variables

Copy the example environment file and configure it:
cp .env.example .env
The default configuration works out of the box for local development:
.env.example
# ---------- server ----------
APP_PORT=8080

# ---------- database ----------
DATABASE_URL=postgres://postgres:postgres@localhost:5432/appdb?sslmode=disable

# ---------- redis / asynq ----------
REDIS_ADDR=localhost:6379
REDIS_PASSWORD=
REDIS_DB=0

# ---------- timeouts ----------
HEALTH_CHECK_TIMEOUT=2s
API_SHUTDOWN_TIMEOUT=10s
WORKER_SHUTDOWN_TIMEOUT=30s

# ---------- logging ----------
# Log output destination: stdout | file | both
LOG_OUTPUT=stdout
# Log file path (required when LOG_OUTPUT is "file" or "both")
LOG_FILE=
3

Start infrastructure services

Launch PostgreSQL and Redis using Docker Compose:
make dev
This command starts the following services:
  • PostgreSQL 16 on port 5432
  • Redis 7 on port 6379
Use make dev-down to stop all services when you’re done
4

Run database migrations

make migrate-up
This sets up your database schema using golang-migrate.

Running the application

Boiler-Go consists of two separate processes that work together:

Start the API server

In your first terminal, start the HTTP API server:
make api
You should see output similar to this:
2024-03-03 10:15:32 INF database connected
2024-03-03 10:15:32 INF redis connected
2024-03-03 10:15:32 INF scheduler client initialized
2024-03-03 10:15:32 INF server starting port=8080
The API server is now running on http://localhost:8080.

Start the background worker

In a second terminal, start the background worker process:
make worker
You should see output similar to this:
2024-03-03 10:16:45 INF database connected
2024-03-03 10:16:45 INF worker starting
The worker is now processing tasks from the Redis queue.

Test your setup

1

Check health status

Verify that all services are running correctly:
curl http://localhost:8080/health
You should receive a response like this:
{
  "status": {
    "database": "up",
    "redis": "up"
  },
  "checked": "2024-03-03T10:17:23Z",
  "duration": 12
}
The duration field shows how long the health check took in milliseconds.
2

Check worker status

Verify that the scheduler is connected:
curl http://localhost:8080/worker/status
Response:
{
  "scheduler": "connected",
  "queues": ["critical", "default", "low"],
  "note": "Use POST /worker/ping to test task processing"
}
3

Test worker task processing

Send a ping task to verify end-to-end functionality:
curl -X POST http://localhost:8080/worker/ping \
  -H "Content-Type: application/json" \
  -H "X-Request-ID: test-123" \
  -d '{"message": "Hello from API"}'
API response:
{
  "success": true,
  "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "task_type": "worker:ping",
  "queued_at": "2024-03-03T10:18:00Z",
  "message": "Task queued successfully. Check worker logs to verify processing."
}
Check your worker terminal to see the task being processed:
2024-03-03 10:18:00 INF task started task_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890 task_type=worker:ping
2024-03-03 10:18:00 INF worker ping task processed - worker is alive! payload="Hello from API" request_id=test-123 task_type=worker:ping
2024-03-03 10:18:00 INF task completed duration=2ms task_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890 task_type=worker:ping
Notice how the request_id is propagated from the API to the worker logs. This enables end-to-end request tracing.

Project structure overview

Here’s what you just set up:
boiler-go/
├── cmd/
│   ├── api/                 # HTTP API server entry point
│   └── worker/              # Background job processor entry point
├── internal/
│   ├── config/              # Environment configuration
│   ├── db/                  # Database connection pool
│   ├── handler/             # HTTP request handlers
│   ├── middleware/          # HTTP middleware (logging, CORS)
│   ├── queue/               # Queue names and priorities
│   ├── scheduler/           # Job scheduling client
│   └── tasks/               # Task type constants
├── pkg/
│   └── logger/              # Structured logging utilities
├── migrations/              # Database migration files
├── Makefile                 # Development commands
├── docker-compose.yml       # Local infrastructure
└── .env                     # Configuration
Learn more about the architecture and design patterns in the architecture guide.

Common development commands

Here are the most frequently used commands:
# Start infrastructure (PostgreSQL + Redis)
make dev

# Stop infrastructure
make dev-down

# Run API server
make api

# Run background worker
make worker

Understanding the code

Let’s look at the API server entry point to understand how everything connects:
cmd/api/main.go
func main() {
    // Load config first with basic logger
    cfg := config.Load(logger.New())
    
    // Create logger based on configuration
    logg := newLogger(cfg, "logs/api.log")
    ctx := context.Background()
    
    // Initialize database pool with timeout context
    dbCtx, dbCancel := context.WithTimeout(ctx, 10*time.Second)
    defer dbCancel()
    if err := db.Open(dbCtx, cfg); err != nil {
        logg.Fatal().Err(err).Msg("failed to initialize database")
    }
    logg.Info().Msg("database connected")
    defer db.Close()
    
    // Initialize Redis client
    rdb := redis.NewClient(&redis.Options{
        Addr:     cfg.RedisAddr,
        Password: cfg.RedisPassword,
        DB:       cfg.RedisDB,
    })
    
    // Initialize scheduler client for worker task enqueueing
    schedulerClient := scheduler.NewClient(asynq.RedisClientOpt{
        Addr:     cfg.RedisAddr,
        Password: cfg.RedisPassword,
        DB:       cfg.RedisDB,
    })
    defer schedulerClient.Close()
    
    // Create router with all dependencies
    router := handler.NewRouter(logg, cfg, db.Get(), rdb, schedulerClient)
    
    // Configure HTTP server
    server := &http.Server{
        Addr:           ":" + cfg.AppPort,
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        IdleTimeout:    60 * time.Second,
        MaxHeaderBytes: 1 << 20, // 1MB
    }
    
    // ... graceful shutdown handling
}
Notice the context-aware initialization pattern. The database connection uses a timeout context to prevent hanging during startup.

Next steps

Now that you have a running backend, explore these topics:

Architecture

Learn about the project structure and design patterns

Configuration

Customize timeouts, logging, and other settings

API reference

Explore all available endpoints

Add handlers

Create your first custom endpoint