v0.6.0 Release Notes¶
Release Date: 2026-04-12
Highlights¶
- Multi-App Platform: Run multiple SaaS apps on shared backend infrastructure with complete data isolation
- Schema-Per-App Isolation: Each app gets its own PostgreSQL schema for database isolation
- X-App-ID Routing: Header-based request routing to registered app backends
Added¶
Multi-App Server (multiapp/)¶
New package for running multiple apps on shared infrastructure:
MultiAppMode: Multiple apps share a server, routed byX-App-IDheaderSingleAppMode: One app runs on dedicated infrastructure- Schema-per-app database isolation using PostgreSQL schemas (
app_prefix) - Redis and in-memory cache implementations with app-scoped prefixes
import "github.com/grokify/coreforge/multiapp"
// Create multi-app server
server, err := multiapp.NewServer(multiapp.Config{
Mode: multiapp.MultiAppMode,
DatabaseURL: os.Getenv("DATABASE_URL"),
RedisURL: os.Getenv("REDIS_URL"), // optional
})
// Register apps
server.RegisterApp(app1.NewBackend(nil))
server.RegisterApp(app2.NewBackend(nil))
server.RegisterApp(app3.NewBackend(nil))
// Run server
server.Run(":8080")
AppBackend Interface¶
Composable interface for app registration with lifecycle hooks:
type AppBackend interface {
// Slug returns unique app identifier (used in X-App-ID header)
Slug() string
// Name returns human-readable app name
Name() string
// Routes returns the app's HTTP routes
Routes(deps Dependencies) chi.Router
// Migrations returns database migrations (optional)
Migrations() []Migration
// OnRegister is called after app registration
OnRegister(ctx context.Context, cfg *AppConfig) error
// OnShutdown is called during graceful shutdown
OnShutdown(ctx context.Context) error
}
CoreForge Integration (multiapp/)¶
Integration helpers connecting multiapp with CoreForge modules:
- Claims Helpers: JWT claims extraction and validation for multi-tenant context
- Session Management: Multi-tenant session helpers with app-scoped storage
- Ent Factory: Create schema-isolated Ent clients from multiapp database
- HTTP Middleware: Authentication and tenant context propagation
// In your handlers:
func (h *Handler) HandleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// App context (from X-App-ID routing)
appCtx := multiapp.AppContextFromContext(ctx)
appCtx.AppID // "app1"
appCtx.DatabaseSchema // "app_app1"
// JWT claims (from auth middleware)
claims := middleware.ClaimsFromContext(ctx)
claims.PrincipalID // user UUID
claims.OrganizationID // org UUID
}
Example App Backend¶
Complete example demonstrating the AppBackend interface:
// multiapp/example/example_app.go
type ExampleBackend struct{}
func (b *ExampleBackend) Slug() string { return "example" }
func (b *ExampleBackend) Name() string { return "Example App" }
func (b *ExampleBackend) Routes(deps multiapp.Dependencies) chi.Router {
r := chi.NewRouter()
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
return r
}
Security¶
Generic 404 Responses¶
The multiapp middleware now returns generic 404 responses for missing or invalid X-App-ID headers. This prevents:
- Information leakage about the routing mechanism
- App ID enumeration attacks
- Revealing which app IDs are valid
// Before: Detailed error messages
// "X-App-ID header required" (400)
// "app \"xyz\" not found" (404)
// After: Generic 404 for all cases
// 404 Not Found
Documentation¶
- Multi-app architecture documentation with deployment patterns (
docs/multiapp/overview.md) - AppBackend implementation guide with code examples
- Schema-per-app isolation explained
Dependencies¶
github.com/mattn/go-sqlite3: 1.14.40 → 1.14.42golang.org/x/crypto: 0.49.0 → 0.50.0
Migration Guide¶
Implementing AppBackend for Existing Apps¶
- Refactor server to accept external database:
// Before: Server creates its own database
func NewServer(cfg Config) (*Server, error) {
db, _ := sql.Open("postgres", cfg.DatabaseURL)
return newServerInternal(cfg, db)
}
// After: Add factory for external database
func NewServerWithDatabase(cfg Config, db *sql.DB) (*Server, error) {
return newServerInternal(cfg, db)
}
func newServerInternal(cfg Config, db *sql.DB) (*Server, error) {
// Shared initialization logic
}
- Create multiapp adapter package (
multiapp/backend.go):
package multiapp
import (
cfmultiapp "github.com/grokify/coreforge/multiapp"
"github.com/yourapp/internal/server"
)
type Backend struct {
server *server.Server
}
func NewBackend(cfg *Config) *Backend {
return &Backend{}
}
func (b *Backend) Slug() string { return "yourapp" }
func (b *Backend) Name() string { return "Your App" }
func (b *Backend) Routes(deps cfmultiapp.Dependencies) chi.Router {
// Create server with schema-isolated database
b.server, _ = server.NewServerWithDatabase(cfg, deps.DB.Pool())
return b.server.Router()
}
func (b *Backend) OnShutdown(ctx context.Context) error {
return b.server.Close()
}
- Move from
internal/multiapptomultiapp(public package):
Testing Multi-App Deployment¶
# Start multi-app server
go run ./cmd/multiapp-server
# Route requests with X-App-ID header
curl -H "X-App-ID: yourapp" http://localhost:8080/api/health
curl -H "X-App-ID: otherapp" http://localhost:8080/api/health
# Requests without X-App-ID return 404
curl http://localhost:8080/api/health
# 404 Not Found
Architecture¶
┌─────────────────────────────────────────────────────────────────┐
│ HTTP Request │
│ X-App-ID: app1 │
└─────────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────────▼───────────────────────────────┐
│ multiapp.Server │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ appContextMiddleware │ │
│ │ - Extracts X-App-ID header │ │
│ │ - Looks up registered app │ │
│ │ - Routes to app's router │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────┬───────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ App1 │ │ App2 │ │ App3 │
│ AppBackend │ │ AppBackend │ │ AppBackend │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ Schema: │ │ Schema: │ │ Schema: │
│ app_app1 │ │ app_app2 │ │ app_app3 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Semver Note¶
This is a minor version bump (v0.5.0 → v0.6.0) as it adds new features without breaking changes. All existing APIs remain backward compatible.