Skip to main content

APIVersionManager Concept

The APIVersionManager coordinates multiple API versions, each with its own:
  • APIRegistry — stores endpoints and generates OpenAPI specs
  • ASTParser — analyzes Go source for that version’s handlers
  • SchemaGenerator — generates component schemas
  • Configuration — title, description, status, public Swagger access
This isolation means v1 and v2 can have completely different endpoints, schemas, and documentation.
Each version gets a separate OpenAPI 3.0.3 spec. Component schemas are pruned per-version — only schemas referenced by that version’s endpoints are included.

VersionedAPIRouter Usage

The VersionedAPIRouter wraps PocketBase’s router and provides version-aware route registration. It automatically:
  • Registers routes to both the runtime router and the documentation registry
  • Tracks request/response schemas via AST analysis
  • Handles middleware binding (.Bind() and .BindFunc())
  • Extracts path parameters from route patterns

Basic Example

func registerV1Routes(router *api.VersionedAPIRouter) {
    router.GET("/api/v1/todos", getTodosHandler)
    router.POST("/api/v1/todos", createTodoHandler).Bind(apis.RequireAuth())
    router.GET("/api/v1/todos/{id}", getTodoHandler)
}
Each method (GET, POST, PUT, PATCH, DELETE) returns a *VersionedRouteChain that supports middleware binding.

Managing Multiple API Versions

Version Configurations

APIDocsConfig holds version-specific settings:
type APIDocsConfig struct {
    Title       string // "My API"
    Version     string // "1.0.0" or "2.0.0"
    Description string
    BaseURL     string // "http://localhost:8090"
    Enabled     bool   // Enable doc generation
    Status      string // "stable", "testing", "deprecated"
    
    // Public Swagger UI access (no auth required)
    PublicSwagger bool
    
    // Contact info
    ContactName  string
    ContactEmail string
    ContactURL   string
    
    // License
    LicenseName string
    LicenseURL  string
    
    // Terms of service
    TermsOfService string
    
    // External docs
    ExternalDocsURL  string
    ExternalDocsDesc string
}

Version Status Levels

StatusMeaningTypical Use
stableProduction-ready, recommended versionDefault for most APIs
testingBeta/preview version, subject to changeNew features under evaluation
deprecatedLegacy version, scheduled for removalMigration period
The Status field is informational only — it appears in the OpenAPI spec’s server description but doesn’t affect routing or access control.

Real Example from routes.go

Here’s how the demo app initializes a versioned system:
func initVersionedSystem() *api.APIVersionManager {
    // Base config shared by all versions
    baseConfig := &api.APIDocsConfig{
        Title:       "pb-ext demo api",
        Description: "Hello world",
        BaseURL:     "http://127.0.0.1:8090/",
        Enabled:     true,
        ContactName:  "pb-ext Team",
        ContactEmail: "[email protected]",
        ContactURL:   "https://github.com/magooney-loon/pb-ext",
        LicenseName: "MIT",
        LicenseURL:  "https://opensource.org/licenses/MIT",
        TermsOfService: "https://example.com/terms",
        ExternalDocsURL:  "https://github.com/magooney-loon/pb-ext",
        ExternalDocsDesc: "pb-ext documentation",
    }

    // v1 config (stable, public Swagger)
    v1Config := *baseConfig
    v1Config.Version = "1.0.0"
    v1Config.Status = "stable"
    v1Config.PublicSwagger = true

    // v2 config (testing, private Swagger)
    v2Config := *baseConfig
    v2Config.Version = "2.0.0"
    v2Config.Status = "testing"
    v2Config.PublicSwagger = false

    // Initialize with configs and route registrars
    return api.InitializeVersionedSystemWithRoutes(map[string]*api.VersionSetup{
        "v1": {
            Config: &v1Config,
            Routes: registerV1Routes,
        },
        "v2": {
            Config: &v2Config,
            Routes: registerV2Routes,
        },
    }, "v1") // "v1" is the default version
}

Server Registration

The version manager must be registered with the PocketBase app to serve endpoints:
func registerRoutes(app core.App) {
    versionManager := initVersionedSystem()

    // Register routes on serve
    app.OnServe().BindFunc(func(e *core.ServeEvent) error {
        if err := versionManager.RegisterAllVersionRoutes(e); err != nil {
            return err
        }
        return e.Next()
    })

    // Register version management endpoints
    versionManager.RegisterWithServer(app)
}
This sets up:
  • All version routes (e.g., /api/v1/todos, /api/v2/time)
  • Version listing endpoint (/api/docs/versions)
  • Per-version OpenAPI endpoints (/api/docs/v1, /api/docs/v2)
  • Public Swagger UI endpoints (if PublicSwagger: true)
  • Debug AST endpoint (/api/docs/debug/ast)

Public vs Private Swagger Access

Public Swagger (PublicSwagger: true)

Exposes two endpoints without authentication:
GET /api/docs/v1/spec     — OpenAPI JSON spec
GET /api/docs/v1/swagger  — Interactive Swagger UI
Ideal for public APIs where you want developers to explore the documentation without creating an account.

Private Swagger (PublicSwagger: false)

Requires superuser authentication to access:
GET /api/docs/v2          — OpenAPI JSON spec (superuser only)
GET /api/docs/v2/swagger  — Not available (404)
If PublicSwagger: false, the /swagger endpoint is not registered at all. Use the authenticated /api/docs/v2 endpoint to retrieve the spec programmatically.

Version Endpoints Reference

List All Versions

GET /api/docs/versions
Authorization: Bearer <superuser_token>
{
  "versions": [
    {
      "version": "v1",
      "status": "stable",
      "created_at": "2024-01-15T10:00:00Z",
      "updated_at": "2024-01-15T10:00:00Z",
      "config": {
        "title": "pb-ext demo api",
        "version": "1.0.0",
        "public_swagger": true
      },
      "endpoints": 5
    },
    {
      "version": "v2",
      "status": "testing",
      "created_at": "2024-01-15T10:00:00Z",
      "updated_at": "2024-01-15T10:00:00Z",
      "config": {
        "title": "pb-ext demo api",
        "version": "2.0.0",
        "public_swagger": false
      },
      "endpoints": 1
    }
  ],
  "default_version": "v1",
  "total_versions": 2,
  "generated_at": "2024-01-15T10:00:00Z"
}

Get Version-Specific OpenAPI Spec

GET /api/docs/v1/spec
Both return the same OpenAPI 3.0.3 JSON spec.

Access Swagger UI

GET /api/docs/v1/swagger
Only available if PublicSwagger: true. Returns an HTML page with embedded Swagger UI (dark mode CSS included).

Advanced Configuration

Dynamic Server URLs

The system automatically constructs server URLs based on the request:
// Configured BaseURL is localhost (dev mode)
BaseURL: "http://localhost:8090"

// Request comes from production domain
Request-Host: api.example.com

// Generated server URL in OpenAPI spec:
{
  "servers": [
    {
      "url": "https://api.example.com/api/v1",
      "description": "API v1 Server (stable)"
    }
  ]
}
If BaseURL is not localhost, the configured value is used. This ensures production specs use the correct domain.

Custom Version Validation

Version strings are validated when registering:
func (vm *APIVersionManager) RegisterVersion(version string, config *APIDocsConfig) error {
    if err := ValidateVersionString(version); err != nil {
        return err // Invalid version format
    }
    // ...
}
Valid formats: v1, v2, v1.0, v2.0.0, etc.

Migration Workflow

When introducing a new API version:
1

Create new version config

Set Status: "testing" and PublicSwagger: false initially.
2

Register routes

Create a new registerV2Routes() function and add it to the version manager.
3

Test internally

Use the authenticated OpenAPI endpoint to validate the spec.
4

Enable public access

Set PublicSwagger: true and Status: "stable" when ready.
5

Deprecate old version

Update v1 to Status: "deprecated" and add a sunset date to the description.

Next Steps