Skip to content
/ medusa Public template

A batteries-included Go framework for building modern, scalable backends πŸͺΌ

License

Notifications You must be signed in to change notification settings

imlargo/medusa

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

236 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸͺΌ Medusa

A batteries-included Go framework for building modern, scalable backends

Go Version License

Features β€’ Quick Start β€’ Architecture β€’ Documentation β€’ Roadmap


What is Medusa?

Medusa is a production-ready framework for Go that eliminates the tedious setup of modern backend applications. It's not just another HTTP router it's a complete platform that integrates everything you need to build enterprise-grade systems.

Stop wasting time on boilerplate. Start building features on day one.

Built for

  • βœ… REST APIs with authentication, validation, and rate limiting
  • βœ… Real-time systems with SSE, WebSockets, and pub/sub
  • βœ… Microservices architectures with clean boundaries
  • βœ… SaaS platforms with storage, email, and notifications
  • βœ… Event-driven applications with message queues
  • βœ… Scalable backends with observability and metrics

Design Philosophy

  • πŸ”‹ Batteries Included, Decisions Optional Everything ready out-of-the-box, but never imposed
  • 🧩 Modular & Composable Use what you need, ignore what you don't
  • πŸ—οΈ Clean Architecture SOLID principles without unnecessary ceremony
  • πŸš€ Fast to Ship, Built to Scale Rapid iteration without technical debt
  • οΏ½οΏ½οΏ½οΏ½ Pragmatic, Not Dogmatic Sensible conventions, always flexible

Features

Core Framework

  • 🎯 Application Lifecycle Graceful shutdown, signal handling, context propagation
  • πŸ—οΈ Bootstrap Architecture DI container with lifecycle hooks and optional components
  • 🌐 HTTP Server Built on Gin with extensible middleware and multiple server support
  • πŸ“ Structured Logging Production-ready logging with Zap
  • πŸ—„οΈ Repository Pattern Clean data layer abstractions with GORM
  • πŸ“Š Observability Prometheus metrics, health checks, and monitoring
  • βš™οΈ Configuration Environment-based config with validation
  • πŸ” Request Tracking Request ID propagation through all layers

Enhanced Context & Error Handling

  • πŸ“¦ Typed Context Parameter extraction with automatic validation
  • βœ… Validation Helpers Built-in pagination, sorting, UUID validation
  • 🎯 Authentication Helpers UserID(), IsAuthenticated() for auth checks
  • πŸ“€ Response Helpers OK(), Created(), Error(), Paged() for consistent responses
  • 🚨 Structured Errors HTTP-aware error system with request ID tracking
  • πŸ”„ Error Wrapping Support for error wrapping and unwrapping

Authentication & Security

  • πŸ” JWT Authentication Token generation, validation, and refresh tokens
  • πŸ”‘ API Key Auth Header and Bearer token strategies
  • πŸ›‘οΈ CORS Configurable cross-origin policies
  • ⏱️ Rate Limiting Token bucket algorithm with per-IP limiting
  • πŸ”’ Middleware Chain Extensible security pipeline

Services (The Batteries)

  • πŸ’Ύ Cache Redis-backed distributed caching with clean interface
  • πŸ“¦ Storage Multi-provider file storage (S3, Cloudflare R2) with presigned URLs
  • πŸ“§ Email Transactional email via Resend
  • πŸ”” Push Notifications Web Push API integration
  • πŸ“‘ Server-Sent Events Real-time server-to-client streaming with client management
  • 🐰 PubSub RabbitMQ message queue with publisher/subscriber pattern
  • πŸ—ƒοΈ Database PostgreSQL with GORM and automatic migrations

Quick Start

Prerequisites

  • Go 1.21 or higher
  • PostgreSQL 14+
  • Redis 7+
  • (Optional) RabbitMQ for message queues

Installation

# Clone the repository
git clone https://github.com/imlargo/medusa. git
cd medusa

# Install dependencies
go mod download

# Copy environment configuration
cp .env.example .env
# Edit .env with your database and Redis credentials

# Run the application
go run cmd/api/main.go

The server will start at http://localhost:8080

Modern Bootstrap Architecture

package main

import (
    "context"
    "log"
    "github.com/imlargo/medusa/cmd/api/bootstrap"
)

func main() {
    // Bootstrap handles all dependency wiring
    app, err := bootstrap.New("medusa-api")
    if err != nil {
        log.Fatal(err)
    }
    defer app.Container.Cleanup()

    // Run with graceful shutdown and error handling
    if err := app.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

The bootstrap system automatically configures:

  • Database connection with connection pooling
  • Optional Redis for caching
  • JWT authentication
  • Health checks (liveness + readiness)
  • Structured logging
  • Metrics collection
  • Rate limiting (if enabled)

Enhanced Context Usage

import "github.com/imlargo/medusa/pkg/medusa"

func GetUsers(c *gin.Context) {
    ctx := medusa.NewContext(c)
    
    // Type-safe parameter extraction with validation
    userID, err := ctx.ParamUUID("id")
    if err != nil {
        // Automatically returns 400 with validation error
        return
    }
    
    // Built-in pagination
    page := ctx.GetPage()      // Defaults to 1
    pageSize := ctx.GetPageSize() // Defaults to 20, max 100
    
    // Authentication helpers
    if !ctx.IsAuthenticated() {
        ctx.Error(medusa.ErrUnauthorized("Login required"))
        return
    }
    
    currentUserID, _ := ctx.UserID()
    
    // Query with context propagation
    users, err := userService.List(c.Request.Context(), page, pageSize)
    if err != nil {
        ctx.Error(medusa.Wrap(err, "Failed to fetch users"))
        return
    }
    
    // Paginated response with request ID tracking
    ctx.Paged(users, page, pageSize, totalCount)
}
package main

import (
    "context"
    "github.com/gin-gonic/gin"
    "github.com/imlargo/medusa/pkg/medusa/core/app"
    "github.com/imlargo/medusa/pkg/medusa/core/logger"
    "github.com/imlargo/medusa/pkg/medusa/core/responses"
    "github.com/imlargo/medusa/pkg/medusa/core/server/http"
)

func main() {
    log := logger.NewLogger()
    defer log.Sync()

    router := gin.Default()
    srv := http.NewServer(router, log,
        http.WithServerHost("localhost"),
        http.WithServerPort("8080"),
    )

    app := app.NewApp(
        app. WithName("my-api"),
        app.WithServer(srv),
    )

    // Define your routes
    router.GET("/ping", func(c *gin.Context) {
        responses.SuccessOK(c, gin. H{"message": "pong"})
    })

    // Run with graceful shutdown
    app.Run(context.Background())
}

Using Services

Cache with Redis

import "github.com/imlargo/medusa/pkg/medusa/services/cache"

redisClient := database.NewRedisClient("redis://localhost:6379")
cache := cache.NewRedisCache(redisClient)

// Set value with expiration
cache.Set(ctx, "user:123", userData, 1*time.Hour)

// Get value
var user User
cache.Get(ctx, "user:123", &user)

File Storage (S3/R2)

import "github.com/imlargo/medusa/pkg/medusa/services/storage"

storage, _ := storage.NewFileStorage(storage.StorageProviderR2, config)

// Upload file
file, _ := storage.Upload("avatars/user-123.jpg", fileReader, "image/jpeg", fileSize)
fmt. Println(file. Url) // Public URL

// Generate presigned URL for secure downloads
url, _ := storage.GetPresignedURL("documents/secret.pdf", 15*time.Minute)

Real-time with Server-Sent Events

import "github.com/imlargo/medusa/pkg/medusa/services/sse"

sseManager := sse. NewSSEManager()

// Client connects
router.GET("/stream", func(c *gin.Context) {
    userID := getUserID(c)
    deviceID := c.Query("device_id")
    
    client, _ := sseManager.Subscribe(c. Request.Context(), userID, deviceID)
    
    c.Header("Content-Type", "text/event-stream")
    c.Header("Cache-Control", "no-cache")
    
    for {
        select {
        case msg := <-client. GetChannel():
            c.SSEvent("message", msg)
            c.Writer. Flush()
        case <-c.Request.Context().Done():
            return
        }
    }
})

// Send event to user (from anywhere in your app)
sseManager.Send(userID, &sse. Message{
    Event: "notification",
    Data:  gin.H{"title": "New message", "body": "You have a new message"},
})

Background Jobs with PubSub

import "github.com/imlargo/medusa/pkg/medusa/services/pubsub"

// Publisher
publisher := pubsub.NewRabbitMQPublisher(config)
publisher.Publish(ctx, "user.registered", UserRegisteredEvent{
    UserID:  123,
    Email:  "user@example.com",
})

// Subscriber
subscriber := pubsub.NewRabbitMQSubscriber(config)
subscriber.Subscribe(ctx, "user.registered", func(msg []byte) error {
    var event UserRegisteredEvent
    json.Unmarshal(msg, &event)
    
    // Send welcome email
    sendWelcomeEmail(event. Email)
    return nil
})

Send Email

import "github.com/imlargo/medusa/pkg/medusa/services/email"

emailService := email.NewResendClient(apiKey)
emailService.SendEmail(&email.SendEmailParams{
    From:    "noreply@myapp.com",
    To:      []string{"user@example.com"},
    Subject: "Welcome to MyApp",
    Html:    "<h1>Welcome! </h1><p>Thanks for joining. </p>",
})

Health Checks

The framework includes built-in health check endpoints with request ID tracking:

// Liveness check - Returns 200 if process is running
GET /health
Response: {
    "status": "healthy",
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

// Readiness check - Returns detailed dependency status
GET /ready
Response: {
    "status": "healthy",
    "checks": {
        "database": "healthy",
        "redis": "healthy"
    },
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

// Returns 503 if dependencies are unhealthy with Retry-After header

The health service automatically checks:

  • Database connectivity with configurable timeout
  • Redis connectivity (if configured)
  • Returns appropriate HTTP status codes for monitoring
  • Includes Retry-After header for unhealthy responses

Architecture

Medusa follows Clean Architecture principles with a pragmatic twist structure without bureaucracy.

.
β”œβ”€β”€ cmd/                       # Application entry points
β”‚   β”œβ”€β”€ api/                  # Main HTTP server
β”‚   β”œβ”€β”€ sse/                  # Dedicated SSE server (optional)
β”‚   └── worker/               # Background workers (future)
β”‚
β”œβ”€β”€ internal/                  # Private application code
β”‚   β”œβ”€β”€ config/               # Configuration management
β”‚   β”œβ”€β”€ database/             # Database connections
β”‚   β”œβ”€β”€ handlers/             # HTTP handlers (controllers)
β”‚   β”œβ”€β”€ models/               # Domain entities
β”‚   β”œβ”€β”€ repository/           # Data access layer
β”‚   β”œβ”€β”€ service/              # Business logic
β”‚   └── store/                # Repository composition
β”‚
└── pkg/medusa/               # πŸͺΌ THE FRAMEWORK (reusable)
    β”œβ”€β”€ core/                 # Core components
    β”‚   β”œβ”€β”€ app/             # Application lifecycle
    β”‚   β”œβ”€β”€ env/             # Environment utilities
    β”‚   β”œβ”€β”€ handler/         # Base handler
    β”‚   β”œβ”€β”€ jwt/             # JWT auth
    β”‚   β”œβ”€β”€ logger/          # Structured logging
    β”‚   β”œβ”€β”€ metrics/         # Observability
    β”‚   β”œβ”€β”€ ratelimiter/     # Rate limiting
    β”‚   β”œβ”€β”€ repository/      # Repository pattern
    β”‚   β”œβ”€β”€ responses/       # HTTP response helpers
    β”‚   └── server/          # Server abstractions
    β”‚
    β”œβ”€β”€ middleware/           # HTTP middleware
    β”‚   β”œβ”€β”€ auth. go          # JWT authentication
    β”‚   β”œβ”€β”€ api_key.go       # API key auth
    β”‚   β”œβ”€β”€ cors.go          # CORS policies
    β”‚   β”œβ”€β”€ metrics.go       # Metrics collection
    β”‚   └── rate_limiter.go  # Rate limiting
    β”‚
    β”œβ”€β”€ services/             # Infrastructure services
    β”‚   β”œβ”€β”€ cache/           # Redis cache
    β”‚   β”œβ”€β”€ email/           # Email service
    β”‚   β”œβ”€β”€ notification/    # Push notifications
    β”‚   β”œβ”€β”€ pubsub/          # Message queue
    β”‚   β”œβ”€β”€ sse/             # Server-Sent Events
    β”‚   └── storage/         # File storage
    β”‚
    └── tools/                # Utilities
        β”œβ”€β”€ bind. go          # Data binding helpers
        └── url.go           # URL utilities

Layer Principles

  • cmd/ Minimal entry point, bootstrap only
  • internal/ Your application-specific code
  • pkg/medusa/ Pure framework, no app dependencies
  • Golden Rule: internal/ imports pkg/medusa/, never the reverse

Modularity

Every component in pkg/medusa is independently usable:

// Use only what you need
import "github.com/imlargo/medusa/pkg/medusa/services/cache"
import "github.com/imlargo/medusa/pkg/medusa/core/logger"

// No need to import the entire framework

Examples

Complete REST API with Auth

func main() {
    log := logger.NewLogger()
    router := gin.Default()
    
    // Database
    db, _ := database.NewPostgresDatabase(os.Getenv("DATABASE_URL"))
    
    // JWT Auth
    jwtAuth := jwt.NewJwt(jwt.Config{Secret: os.Getenv("JWT_SECRET")})
    
    // Public routes
    router.POST("/auth/register", RegisterHandler)
    router.POST("/auth/login", LoginHandler)
    
    // Protected routes
    protected := router.Group("/api")
    protected.Use(middleware.AuthTokenMiddleware(jwtAuth))
    {
        protected.GET("/profile", GetProfileHandler)
        protected.PUT("/profile", UpdateProfileHandler)
    }
    
    // Rate limited routes
    limited := router.Group("/api/actions")
    limited.Use(middleware.NewRateLimiterMiddleware(rateLimiter))
    {
        limited.POST("/upload", UploadHandler)
    }
    
    srv := http.NewServer(router, log)
    app := app.NewApp(app.WithServer(srv))
    app.Run(context.Background())
}

Multi-Server Application

// Run HTTP and SSE servers concurrently
func main() {
    log := logger.NewLogger()
    
    // HTTP Server
    httpRouter := gin.Default()
    httpServer := http.NewServer(httpRouter, log,
        http.WithServerPort("8080"),
    )
    
    // SSE Server
    sseRouter := gin.Default()
    sseServer := http.NewServer(sseRouter, log,
        http.WithServerPort("8081"),
    )
    
    // Application with multiple servers
    app := app. NewApp(
        app.WithName("multi-server"),
        app.WithServer(httpServer, sseServer),
    )
    
    // Both servers managed with single graceful shutdown
    app.Run(context.Background())
}

Repository Pattern

// Define repository interface
type UserRepository interface {
    Create(ctx context.Context, user *User) error
    FindByID(ctx context.Context, id uint) (*User, error)
    Update(ctx context.Context, user *User) error
}

// Implementation
type userRepository struct {
    *medusarepo.Repository
}

func NewUserRepository(repo *medusarepo.Repository) UserRepository {
    return &userRepository{Repository: repo}
}

func (r *userRepository) Create(ctx context.Context, user *User) error {
    return r. DB.WithContext(ctx).Create(user).Error
}

func (r *userRepository) FindByID(ctx context.Context, id uint) (*User, error) {
    var user User
    err := r. DB.WithContext(ctx).First(&user, id).Error
    return &user, err
}

Configuration

Medusa uses environment variables for configuration. Create a .env file:

# Server
SERVER_HOST=localhost
SERVER_PORT=8080

# Database
DATABASE_URL=postgres://user:password@localhost:5432/dbname? sslmode=disable

# Redis
REDIS_URL=redis://localhost:6379

# JWT
JWT_SECRET=your-secret-key-change-this
JWT_TOKEN_EXPIRATION=15m
JWT_REFRESH_EXPIRATION=168h

# Rate Limiting
RATE_LIMITER_ENABLED=true
RATE_LIMITER_REQUESTS_PER_TIME_FRAME=100
RATE_LIMITER_TIME_FRAME=60s

# Storage (S3/R2)
STORAGE_PROVIDER=r2
STORAGE_ACCOUNT_ID=your_account_id
STORAGE_ACCESS_KEY_ID=your_access_key
STORAGE_SECRET_ACCESS_KEY=your_secret_key
STORAGE_BUCKET_NAME=your_bucket
STORAGE_USE_PUBLIC_URL=true

# Email (Resend)
RESEND_API_KEY=re_your_api_key

# RabbitMQ
RABBITMQ_URL=amqp://guest:guest@localhost:5672/

Middleware

Available Middleware

import "github.com/imlargo/medusa/pkg/medusa/middleware"

// JWT Authentication
router.Use(middleware.AuthTokenMiddleware(jwtAuth))

// API Key Authentication (Header)
router.Use(middleware.ApiKeyMiddleware("your-api-key"))

// API Key Authentication (Bearer)
router.Use(middleware.BearerApiKeyMiddleware("your-api-key"))

// CORS
router.Use(middleware.NewCorsMiddleware("https://myapp.com", []string{
    "https://app.myapp.com",
}))

// Rate Limiting
router.Use(middleware.NewRateLimiterMiddleware(rateLimiter))

// Metrics Collection
router.Use(middleware.NewMetricsMiddleware(metricsService))

Creating Custom Middleware

func LogRequestMiddleware(log *logger.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        c.Next()
        
        duration := time.Since(start)
        log.Info("Request completed",
            zap.String("method", c.Request.Method),
            zap.String("path", c.Request.URL. Path),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("duration", duration),
        )
    }
}

Development

Hot Reload

Medusa includes Air for hot reload during development:

# Install Air (if not already installed)
go install github.com/cosmtrek/air@latest

# Run with hot reload
air

Configuration is in .air.toml.

Available Commands

# Run the application
make run

# Run with hot reload
make dev

# Format code
make format

# Run tests
make test

# Build binary
make build

# Generate Swagger docs
make swag

# Clean build artifacts
make clean

Testing

package handlers_test

import (
    "testing"
    "net/http/httptest"
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)

func TestPingHandler(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    router := gin.Default()
    router.GET("/ping", PingHandler)
    
    req := httptest.NewRequest("GET", "/ping", nil)
    w := httptest.NewRecorder()
    
    router.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
    assert.Contains(t, w.Body.String(), "pong")
}

Roadmap

βœ… v0.1 - Foundation (Current)

  • Core framework (app, server, logger)
  • JWT & API key authentication
  • Repository pattern with GORM
  • Redis cache
  • File storage (S3/R2)
  • Server-Sent Events
  • PubSub with RabbitMQ
  • Email & push notifications
  • Rate limiting & CORS
  • Prometheus metrics

🚧 v0.2 - Developer Experience (In Progress)

  • Health checks & readiness probes
  • Request ID middleware
  • Enhanced error handling with request tracking
  • Bootstrap architecture with DI container
  • Typed context with validation helpers
  • Comprehensive documentation
  • Example applications
  • Testing utilities
  • Docker Compose setup
  • CI/CD examples

🎯 v0.3 - Validation & Documentation

  • Automatic request validation
  • Declarative validation tags
  • OpenAPI 3.0 generation
  • Swagger UI at /docs
  • ReDoc integration
  • Auto-generated examples

🎯 v0.4 - Type Safety & Ergonomics

  • Enhanced medusa.Context with helpers
  • Type-safe handlers with generics
  • Dependency injection system
  • Automatic pagination
  • Query filter builders
  • File upload helpers

🎯 v0.5 - CLI & Generators

  • medusa CLI tool
  • Project scaffolding
  • Code generators (handlers, models, repos)
  • Migration management
  • Custom templates

🎯 v0.6 - Testing Framework

  • Built-in testing utilities
  • Test database helpers
  • HTTP testing tools
  • Mock generators
  • Fixture factories

🎯 v0.7 - Background Processing

  • Job queue system
  • Scheduled tasks (cron)
  • Worker pools
  • Retry policies
  • Job monitoring

🎯 v0.8 - Advanced Observability

  • OpenTelemetry integration
  • Distributed tracing
  • APM metrics
  • Error tracking
  • Performance profiling

🎯 v0.9 - WebSockets

  • Native WebSocket support
  • Rooms & namespaces
  • Broadcasting
  • Presence detection

🎯 v1.0 - Production Ready

  • Security audit
  • Performance benchmarks
  • Complete documentation
  • Video tutorials
  • Production case studies

Why Medusa?

vs. Minimalist Frameworks (Gin, Echo, Fiber)

Gin/Echo/Fiber are excellent routers, but you still need to:

  • Set up database connections
  • Configure cache
  • Implement auth
  • Add file storage
  • Set up observability
  • Wire everything together

Medusa gives you all of this out-of-the-box, with clean interfaces and best practices baked in.

vs. Full-Stack Frameworks (Buffalo, Beego)

Buffalo/Beego are comprehensive but:

  • Include frontend tooling you might not need
  • More opinionated and harder to customize
  • Heavier and less modular

Medusa focuses on backends only, stays modular, and never forces decisions.

vs. Starting from Scratch

Building your own means:

  • Weeks of setup before writing features
  • Reinventing patterns (repository, cache, storage)
  • Maintenance burden for infrastructure code
  • No community or shared knowledge

Medusa lets you ship on day one with production-ready patterns.


Contributing

Contributions are welcome! Whether it's:

  • πŸ› Bug reports
  • πŸ’‘ Feature requests
  • πŸ“– Documentation improvements
  • πŸ”§ Code contributions

Please open an issue first to discuss what you'd like to change.

Development Setup

# Fork and clone
git clone https://github.com/yourusername/medusa.git
cd medusa

# Install dependencies
go mod download

# Create a branch
git checkout -b feature/amazing-feature

# Make your changes and test
make test

# Commit and push
git commit -m "Add amazing feature"
git push origin feature/amazing-feature

Then open a Pull Request!


License

This project is licensed under the MIT License - see the LICENSE file for details.


Community

  • GitHub Issues Bug reports and feature requests
  • GitHub Discussions Questions and community chat
  • Twitter @yourusername for updates

Acknowledgments

Medusa stands on the shoulders of giants:


Built with ❀️ by imlargo

Get Started β€’ Report Bug β€’ Request Feature

About

A batteries-included Go framework for building modern, scalable backends πŸͺΌ

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •