Overview
Themultitenancy middleware extracts and provides tenant information for multi-tenant SaaS applications. It supports various resolution strategies including subdomain, header, path, and query parameters.
Installation
Quick Start
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
Resolver | Resolver | Subdomain | Tenant resolution function |
ErrorHandler | func(*mizu.Ctx, error) error | - | Error handler |
Required | bool | true | Require tenant resolution |
Tenant Structure
Resolution Strategies
Subdomain Resolution
Header Resolution
Path Resolution
Query Parameter Resolution
Examples
Basic Subdomain Tenant
Database Lookup
Chain Multiple Resolvers
Custom Error Handler
Optional Tenant
MustGet (Panic on Missing)
Custom Resolver
Tenant-Scoped Database
Tenant Middleware Chain
Tenant Metadata
Built-in Resolvers
| Resolver | Source | Example |
|---|---|---|
SubdomainResolver() | Subdomain | tenant1.example.com |
HeaderResolver(h) | HTTP Header | X-Tenant-ID: tenant1 |
PathResolver() | URL Path | /tenant1/api/users |
QueryResolver(p) | Query Param | ?tenant=tenant1 |
ChainResolver(...) | Multiple | Tries each in order |
LookupResolver(r, fn) | Database | Enriches with lookup |
API Reference
Technical Details
Architecture
The multitenancy middleware uses Go’s context package to store and retrieve tenant information throughout the request lifecycle. It implements a resolver pattern that allows flexible tenant identification strategies.Context Storage
The middleware stores tenant information in the request context using a privatecontextKey{} type. This ensures type safety and prevents key collisions with other middleware or application code.
Resolver Chain
The resolver pattern allows composing multiple tenant identification strategies:- Each resolver implements the
Resolverfunction type - Resolvers return
(*Tenant, error)allowing error propagation ChainResolvertries resolvers sequentially until one succeedsLookupResolverwraps a resolver with a database lookup function
Path Rewriting
ThePathResolver automatically rewrites the request path to remove the tenant prefix:
Error Handling
The middleware supports both required and optional tenant resolution:- Required mode (default): Returns error via
ErrorHandlerwhen tenant not found - Optional mode: Continues request processing with
niltenant - Custom error handler: Allows application-specific error responses
Performance Considerations
- Context lookups are O(1) operations
- Subdomain parsing uses efficient string operations
- Header lookups use Go’s optimized HTTP header map
- Consider caching with
LookupResolverfor database-backed tenants
Best Practices
- Use subdomain resolution for user-friendly URLs
- Implement database lookup for rich tenant data
- Use chain resolver for flexibility
- Always validate tenant access to resources
- Include tenant ID in all database queries
- Cache tenant lookups for performance
Testing
The middleware includes comprehensive test coverage for all resolvers and scenarios:| Test Case | Description | Expected Behavior |
|---|---|---|
TestNew | Basic middleware creation with custom resolver | Tenant stored in context and accessible via Get() |
TestWithOptions_Required | Required tenant resolution fails | Returns 400 Bad Request when tenant not found |
TestWithOptions_NotRequired | Optional tenant resolution | Continues without tenant, Get() returns nil |
TestSubdomainResolver | Extract tenant from subdomain | Resolves “acme” from “acme.example.com” |
TestHeaderResolver | Extract tenant from HTTP header | Resolves tenant from “X-Tenant-Id” header |
TestPathResolver | Extract tenant from URL path prefix | Resolves “acme” from “/acme/api/users” and rewrites path to “/api/users” |
TestQueryResolver | Extract tenant from query parameter | Resolves tenant from “?tenant=my-tenant” |
TestLookupResolver | Database lookup enrichment | Enhances basic tenant with full data from lookup function |
TestChainResolver | Multiple resolver fallback | Tries header, then query, then subdomain in order |
TestFromContext | Alias function for Get | FromContext() returns same tenant as Get() |
TestMustGet | Get tenant or panic | Returns tenant when present |
TestMustGet_Panic | Panic on missing tenant | Panics with “tenant not found” message when tenant absent |
TestErrors | Error constants | Validates error messages for ErrTenantNotFound and ErrTenantInvalid |
TestWithOptions_ErrorHandler | Custom error handling | Uses custom error handler to return 401 Unauthorized |