OpenAPI

Auto-generate OpenAPI 3.1 specifications from your routes

Forge automatically generates an OpenAPI 3.1.0 specification from your route definitions. Request and response schemas are derived from Go types, struct tags drive parameter classification, and the spec is served at /_/openapi.

How It Works

When you define routes with schema options, Forge inspects your Go types at startup and builds a complete OpenAPI specification. No code generation step is required -- the spec is always in sync with your code.

router.POST("/users", createUser,
    forge.WithRequestSchema(&CreateUserRequest{}),
    forge.WithResponseSchema(201, "User created", &User{}),
    forge.WithTags("users"),
    forge.WithSummary("Create a new user"),
)

Access the generated spec at:

GET /_/openapi

Request Schemas with Unified Struct Tags

WithRequestSchema is the recommended approach. Forge automatically classifies struct fields based on their tags:

TagClassificationExample
path:"name"Path parameterpath:"userId"
query:"name"Query parameterquery:"page"
header:"name"Header parameterheader:"X-API-Key"
json:"name" or body:""Request body fieldjson:"email"
type CreateUserRequest struct {
    // Path parameter
    TenantID string `path:"tenantId" description:"Tenant identifier" format:"uuid"`

    // Query parameter
    DryRun bool `query:"dryRun" description:"Preview mode without persisting"`

    // Header parameter
    APIKey string `header:"X-API-Key" description:"API authentication key"`

    // Body fields (json tag = body)
    Name  string `json:"name" description:"Full name" minLength:"1" maxLength:"100"`
    Email string `json:"email" description:"Email address" format:"email"`
    Age   int    `json:"age" description:"User age" minimum:"0" maximum:"150"`
}

router.POST("/tenants/:tenantId/users", createUser,
    forge.WithRequestSchema(&CreateUserRequest{}),
)

If a struct has no path, query, or header tags, the entire struct is treated as a JSON request body for backward compatibility.

Validation Tags

Struct tags also drive OpenAPI validation constraints:

type ProductRequest struct {
    Name     string  `json:"name" minLength:"1" maxLength:"200"`
    Price    float64 `json:"price" minimum:"0.01" maximum:"999999.99"`
    Quantity int     `json:"quantity" minimum:"0"`
    SKU      string  `json:"sku" pattern:"^[A-Z]{2}-[0-9]{6}$"`
    Tags     []string `json:"tags" minItems:"1" maxItems:"10"`
}

Response Schemas

Define response schemas for each status code using WithResponseSchema.

router.GET("/users/:id", getUser,
    forge.WithResponseSchema(200, "User found", &User{}),
    forge.WithResponseSchema(404, "User not found", &ErrorResponse{}),
)

Response Helpers

Forge provides convenience functions for common response patterns.

// Generates: { data: T[], total, page, pageSize, totalPages }
router.GET("/users", listUsers,
    forge.WithPaginatedResponse(&User{}, 200),
)
// Adds 200, 201, 204, 400, 401, 403, 404, 500
router.POST("/users", createUser,
    forge.WithStandardRESTResponses(&User{}),
)
// Generates: { jobId, status, statusUrl }
router.POST("/exports", startExport,
    forge.WithAcceptedResponse(),
)
// Generates: { successful, failed, totalProcessed, successCount, failureCount }
router.POST("/users/batch", batchCreate,
    forge.WithBatchResponse(&User{}, 200),
)

Full Response Helpers Reference

HelperStatusDescription
WithResponseSchema(code, desc, type)AnyCustom response schema
WithPaginatedResponse(itemType, code)AnyPaginated list with metadata
WithStandardRESTResponses(type)200/201/204 + errorsFull CRUD response set
WithErrorResponses()400/401/403/404/500Standard error responses
WithCreatedResponse(type)201Resource created
WithNoContentResponse()204No content (deletes)
WithAcceptedResponse()202Async operation accepted
WithListResponse(itemType, code)AnySimple array response
WithBatchResponse(itemType, code)AnyBatch operation result
WithFileUploadResponse(code)AnyFile upload success
WithValidationErrorResponse()422Validation error details

Request and Response Examples

Provide example values that appear in the OpenAPI spec and generated documentation.

router.POST("/users", createUser,
    forge.WithRequestSchema(&CreateUserRequest{}),
    forge.WithRequestExample("basic", CreateUserRequest{
        Name:  "Alice Smith",
        Email: "alice@example.com",
        Age:   30,
    }),
    forge.WithResponseSchema(201, "Created", &User{}),
    forge.WithResponseExample(201, "success", User{
        ID:    "usr_abc123",
        Name:  "Alice Smith",
        Email: "alice@example.com",
    }),
)

Discriminators for Polymorphic Types

Support polymorphic request/response types using OpenAPI discriminators with oneOf.

type Notification struct {
    Type string `json:"type"` // "email", "sms", "push"
}

type EmailNotification struct {
    Notification
    Subject string `json:"subject"`
    Body    string `json:"body"`
}

type SMSNotification struct {
    Notification
    PhoneNumber string `json:"phone_number"`
    Message     string `json:"message"`
}

router.POST("/notifications", sendNotification,
    forge.WithRequestSchema(&Notification{}),
    forge.WithDiscriminator(forge.DiscriminatorConfig{
        PropertyName: "type",
        Mapping: map[string]any{
            "email": &EmailNotification{},
            "sms":   &SMSNotification{},
        },
    }),
)

Content Types

Specify custom content types for request and response bodies.

router.POST("/upload", uploadHandler,
    forge.WithRequestContentTypes("multipart/form-data"),
    forge.WithResponseContentTypes("application/json", "application/xml"),
)

Schema References

Register reusable schemas in the OpenAPI components section.

router.GET("/users/:id", getUser,
    forge.WithSchemaRef("User", &User{}),
    forge.WithSchemaRef("Error", &ErrorResponse{}),
    forge.WithResponseSchema(200, "Success", &User{}),
)

Route Metadata

Add OpenAPI metadata to routes for better documentation.

router.GET("/users/:id", getUser,
    forge.WithName("getUser"),
    forge.WithOperationID("getUserById"),
    forge.WithSummary("Get a user by ID"),
    forge.WithDescription("Retrieves a user record by their unique identifier."),
    forge.WithTags("users", "admin"),
    forge.WithDeprecated(),
    forge.WithExternalDocs("API Guide", "https://docs.example.com/users"),
    forge.WithSecurity("bearerAuth"),
)

Excluding Routes from OpenAPI

Prevent specific routes or groups from appearing in the OpenAPI spec.

// Exclude a single route
router.GET("/internal/debug", debugHandler,
    forge.WithOpenAPIExclude(),
)

// Exclude all schemas (OpenAPI, AsyncAPI, oRPC)
router.GET("/internal/status", statusHandler,
    forge.WithSchemaExclude(),
)

// Exclude an entire group
adminGroup := router.Group("/admin",
    forge.WithGroupSchemaExclude(),
)

OpenAPI Configuration

Configure the OpenAPI spec globally through OpenAPIConfig on the router.

app := forge.New(
    forge.WithAppRouterOptions(
        forge.WithOpenAPI(forge.OpenAPIConfig{
            Title:       "My API",
            Version:     "2.0.0",
            Description: "Production API for My Service",
            Servers: []forge.OpenAPIServer{
                {URL: "https://api.example.com", Description: "Production"},
                {URL: "https://staging-api.example.com", Description: "Staging"},
            },
            Contact: &forge.Contact{
                Name:  "API Support",
                Email: "api@example.com",
                URL:   "https://example.com/support",
            },
            License: &forge.License{
                Name: "MIT",
                URL:  "https://opensource.org/licenses/MIT",
            },
            SecuritySchemes: map[string]forge.SecurityScheme{
                "bearerAuth": {
                    Type:         "http",
                    Scheme:       "bearer",
                    BearerFormat: "JWT",
                },
                "apiKey": {
                    Type: "apiKey",
                    In:   "header",
                    Name: "X-API-Key",
                },
            },
            Tags: []forge.OpenAPITag{
                {Name: "users", Description: "User management operations"},
                {Name: "orders", Description: "Order processing"},
            },
        }),
    ),
)

Complete Example

package main

import "github.com/xraph/forge"

type CreateUserRequest struct {
    TenantID string `path:"tenantId" format:"uuid"`
    Name     string `json:"name" minLength:"1" maxLength:"100"`
    Email    string `json:"email" format:"email"`
    Role     string `json:"role" enum:"admin,user,viewer"`
}

type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Role  string `json:"role"`
}

func main() {
    app := forge.New(
        forge.WithAppName("user-api"),
    )

    r := app.Router()

    r.POST("/tenants/:tenantId/users", createUser,
        forge.WithSummary("Create user"),
        forge.WithDescription("Creates a new user in the specified tenant."),
        forge.WithTags("users"),
        forge.WithRequestSchema(&CreateUserRequest{}),
        forge.WithCreatedResponse(&User{}),
        forge.WithErrorResponses(),
        forge.WithValidationErrorResponse(),
        forge.WithRequestExample("basic", CreateUserRequest{
            Name:  "Alice",
            Email: "alice@example.com",
            Role:  "user",
        }),
    )

    app.Run()
}

func createUser(ctx forge.Context) error {
    var req CreateUserRequest
    if err := ctx.BindJSON(&req); err != nil {
        return forge.BadRequest("invalid request body")
    }
    req.TenantID = ctx.Param("tenantId")

    // ... create user logic
    return ctx.JSON(201, User{
        ID:    "usr_new",
        Name:  req.Name,
        Email: req.Email,
        Role:  req.Role,
    })
}

The generated OpenAPI spec is available at GET /_/openapi and can be used with Swagger UI, Redoc, or any OpenAPI-compatible tool.

How is this guide?

On this page