A batteries-included Go framework for building modern, scalable backends
Features β’ Quick Start β’ Architecture β’ Documentation β’ Roadmap
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.
- β 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
- π 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
- π― 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
- π¦ 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
- π 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
- πΎ 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
- Go 1.21 or higher
- PostgreSQL 14+
- Redis 7+
- (Optional) RabbitMQ for message queues
# 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.goThe server will start at http://localhost:8080
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)
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())
}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)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)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"},
})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
})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>",
})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 headerThe 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
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
cmd/Minimal entry point, bootstrap onlyinternal/Your application-specific codepkg/medusa/Pure framework, no app dependencies- Golden Rule:
internal/importspkg/medusa/, never the reverse
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 frameworkfunc 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())
}// 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())
}// 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
}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/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))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),
)
}
}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
airConfiguration is in .air.toml.
# 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 cleanpackage 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")
}- 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
- 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
- Automatic request validation
- Declarative validation tags
- OpenAPI 3.0 generation
- Swagger UI at
/docs - ReDoc integration
- Auto-generated examples
- Enhanced
medusa.Contextwith helpers - Type-safe handlers with generics
- Dependency injection system
- Automatic pagination
- Query filter builders
- File upload helpers
-
medusaCLI tool - Project scaffolding
- Code generators (handlers, models, repos)
- Migration management
- Custom templates
- Built-in testing utilities
- Test database helpers
- HTTP testing tools
- Mock generators
- Fixture factories
- Job queue system
- Scheduled tasks (cron)
- Worker pools
- Retry policies
- Job monitoring
- OpenTelemetry integration
- Distributed tracing
- APM metrics
- Error tracking
- Performance profiling
- Native WebSocket support
- Rooms & namespaces
- Broadcasting
- Presence detection
- Security audit
- Performance benchmarks
- Complete documentation
- Video tutorials
- Production case studies
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.
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.
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.
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.
# 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-featureThen open a Pull Request!
This project is licensed under the MIT License - see the LICENSE file for details.
- GitHub Issues Bug reports and feature requests
- GitHub Discussions Questions and community chat
- Twitter @yourusername for updates
Medusa stands on the shoulders of giants:
Built with β€οΈ by imlargo
Get Started β’ Report Bug β’ Request Feature