OpenAPI
Generate OpenAPI/Swagger documentation automatically with Forge's built-in support
Forge provides comprehensive OpenAPI (Swagger) support with automatic documentation generation, schema validation, and interactive API exploration. The framework generates OpenAPI specifications from your route definitions and provides built-in endpoints for API documentation.
OpenAPI Interface
Forge's OpenAPI interface provides automatic documentation generation:
type OpenAPIManager interface {
// Schema generation
GenerateSpec() (*openapi3.T, error)
AddSchema(name string, schema interface{}) error
// Route documentation
DocumentRoute(route RouteInfo) error
DocumentGroup(group GroupInfo) error
// Customization
SetInfo(info OpenAPIInfo) error
SetServers(servers []OpenAPIServer) error
// Validation
ValidateRequest(schema interface{}, data interface{}) error
ValidateResponse(schema interface{}, data interface{}) error
}Automatic Documentation Generation
Basic OpenAPI Setup
func main() {
app := forge.NewApp(forge.AppConfig{
Name: "my-api",
Version: "1.0.0",
})
// Enable OpenAPI documentation
app.Router().Use(forge.OpenAPIMiddleware(forge.OpenAPIConfig{
Title: "My API",
Version: "1.0.0",
Description: "A comprehensive API for managing resources",
Contact: forge.OpenAPIContact{
Name: "API Support",
Email: "support@example.com",
},
License: forge.OpenAPILicense{
Name: "MIT",
URL: "https://opensource.org/licenses/MIT",
},
}))
// Your routes will be automatically documented
app.Router().GET("/users", getUsersHandler)
app.Router().POST("/users", createUserHandler)
app.Run()
}Route Documentation
// Documented route with schemas
app.Router().POST("/users", createUserHandler,
forge.WithRequestSchema(CreateUserRequest{}),
forge.WithResponseSchema(201, "User created successfully", UserResponse{}),
forge.WithResponseSchema(400, "Bad request", ErrorResponse{}),
forge.WithResponseSchema(422, "Validation error", ValidationErrorResponse{}),
forge.WithTags("users"),
forge.WithSummary("Create a new user"),
forge.WithDescription("Creates a new user account with the provided information"),
)
// Documented route with path parameters
app.Router().GET("/users/:id", getUserHandler,
forge.WithResponseSchema(200, "User details", UserResponse{}),
forge.WithResponseSchema(404, "User not found", ErrorResponse{}),
forge.WithTags("users"),
forge.WithSummary("Get user by ID"),
forge.WithDescription("Retrieves user information by user ID"),
)Schema Definitions
Request/Response Schemas
// User schemas
type User struct {
ID uuid.UUID `json:"id" description:"Unique user identifier" format:"uuid"`
Email string `json:"email" description:"User email address" format:"email"`
Name string `json:"name" description:"User full name" minLength:"2" maxLength:"100"`
CreatedAt time.Time `json:"created_at" description:"User creation timestamp" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" description:"User last update timestamp" format:"date-time"`
}
type CreateUserRequest struct {
Email string `json:"email" description:"User email address" format:"email" validate:"required,email"`
Name string `json:"name" description:"User full name" minLength:"2" maxLength:"100" validate:"required"`
Password string `json:"password" description:"User password" minLength:"8" validate:"required"`
}
type UpdateUserRequest struct {
Name string `json:"name" description:"User full name" minLength:"2" maxLength:"100"`
Password string `json:"password" description:"User password" minLength:"8"`
}
type UserResponse struct {
ID uuid.UUID `json:"id" description:"Unique user identifier" format:"uuid"`
Email string `json:"email" description:"User email address" format:"email"`
Name string `json:"name" description:"User full name"`
CreatedAt time.Time `json:"created_at" description:"User creation timestamp" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" description:"User last update timestamp" format:"date-time"`
}
type ErrorResponse struct {
Error string `json:"error" description:"Error message"`
Code int `json:"code" description:"Error code"`
Details string `json:"details,omitempty" description:"Additional error details"`
}Complex Schemas
// Pagination schema
type PaginatedResponse struct {
Data []UserResponse `json:"data" description:"Array of users"`
Pagination PaginationInfo `json:"pagination" description:"Pagination information"`
}
type PaginationInfo struct {
Page int `json:"page" description:"Current page number" minimum:"1"`
PerPage int `json:"per_page" description:"Items per page" minimum:"1" maximum:"100"`
Total int `json:"total" description:"Total number of items"`
TotalPages int `json:"total_pages" description:"Total number of pages"`
HasNext bool `json:"has_next" description:"Whether there is a next page"`
HasPrev bool `json:"has_prev" description:"Whether there is a previous page"`
}
// Filter schema
type UserFilter struct {
Name string `json:"name,omitempty" description:"Filter by name (partial match)"`
Email string `json:"email,omitempty" description:"Filter by email (partial match)"`
CreatedAfter *time.Time `json:"created_after,omitempty" description:"Filter users created after this date" format:"date-time"`
CreatedBefore *time.Time `json:"created_before,omitempty" description:"Filter users created before this date" format:"date-time"`
}Advanced OpenAPI Features
Custom Schema References
// Define reusable schemas
app.Router().AddSchemaRef("User", User{})
app.Router().AddSchemaRef("Error", ErrorResponse{})
app.Router().AddSchemaRef("Pagination", PaginationInfo{})
// Use schema references in routes
app.Router().GET("/users", getUsersHandler,
forge.WithResponseSchema(200, "List of users", forge.SchemaRef("PaginatedResponse")),
forge.WithResponseSchema(400, "Bad request", forge.SchemaRef("Error")),
)Request Examples
app.Router().POST("/users", createUserHandler,
forge.WithRequestSchema(CreateUserRequest{}),
forge.WithRequestExample("valid_user", CreateUserRequest{
Email: "john@example.com",
Name: "John Doe",
Password: "securepassword123",
}),
forge.WithRequestExample("minimal_user", CreateUserRequest{
Email: "jane@example.com",
Name: "Jane",
Password: "password123",
}),
)Response Examples
app.Router().GET("/users/:id", getUserHandler,
forge.WithResponseSchema(200, "User details", UserResponse{}),
forge.WithResponseExample(200, "successful_user", UserResponse{
ID: uuid.New(),
Email: "john@example.com",
Name: "John Doe",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}),
forge.WithResponseSchema(404, "User not found", ErrorResponse{}),
forge.WithResponseExample(404, "not_found", ErrorResponse{
Error: "User not found",
Code: 404,
}),
)Authentication Documentation
// Define security schemes
app.Router().AddSecurityScheme("BearerAuth", forge.SecurityScheme{
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
Description: "JWT token authentication",
})
app.Router().AddSecurityScheme("ApiKeyAuth", forge.SecurityScheme{
Type: "apiKey",
In: "header",
Name: "X-API-Key",
Description: "API key authentication",
})
// Apply security to routes
app.Router().GET("/users", getUsersHandler,
forge.WithAuth("BearerAuth"),
forge.WithResponseSchema(200, "List of users", []UserResponse{}),
forge.WithResponseSchema(401, "Unauthorized", ErrorResponse{}),
)
app.Router().POST("/users", createUserHandler,
forge.WithAuth("ApiKeyAuth"),
forge.WithRequestSchema(CreateUserRequest{}),
forge.WithResponseSchema(201, "User created", UserResponse{}),
)OpenAPI Endpoints
Built-in Endpoints
Forge automatically provides OpenAPI endpoints:
# OpenAPI specification
GET /_/openapi.json
# Swagger UI
GET /_/swagger
# ReDoc documentation
GET /_/redoc
# OpenAPI YAML
GET /_/openapi.yamlCustom Documentation Endpoints
// Custom OpenAPI endpoint
app.Router().GET("/docs/openapi", func(ctx forge.Context) error {
spec := app.Router().GenerateOpenAPISpec()
// Customize spec
spec.Info.Title = "My Custom API"
spec.Info.Version = "2.0.0"
return ctx.JSON(200, spec)
})
// Custom Swagger UI
app.Router().GET("/docs/swagger", func(ctx forge.Context) error {
html := generateSwaggerUI("/docs/openapi")
return ctx.HTML(200, html)
})Schema Validation
Request Validation
func createUserHandler(ctx forge.Context) error {
var req CreateUserRequest
// Bind and validate request
if err := ctx.BindJSON(&req); err != nil {
return forge.BadRequest("invalid JSON")
}
// Validate against OpenAPI schema
if err := app.Router().ValidateRequest(CreateUserRequest{}, req); err != nil {
return forge.BadRequest("validation failed")
}
// Process request
user, err := userService.CreateUser(req)
if err != nil {
return forge.InternalError("failed to create user")
}
return ctx.JSON(201, user)
}Response Validation
func getUserHandler(ctx forge.Context) error {
id := ctx.Param("id")
user, err := userService.GetUser(id)
if err != nil {
return forge.NotFound("user not found")
}
// Validate response against schema
if err := app.Router().ValidateResponse(UserResponse{}, user); err != nil {
app.Logger().Warn("response validation failed", forge.F("error", err))
}
return ctx.JSON(200, user)
}OpenAPI Configuration
Basic Configuration
app := forge.NewApp(forge.AppConfig{
OpenAPIConfig: forge.OpenAPIConfig{
Enabled: true,
Title: "My API",
Version: "1.0.0",
Description: "A comprehensive API for managing resources",
Contact: forge.OpenAPIContact{
Name: "API Support",
Email: "support@example.com",
URL: "https://example.com/support",
},
License: forge.OpenAPILicense{
Name: "MIT",
URL: "https://opensource.org/licenses/MIT",
},
Servers: []forge.OpenAPIServer{
{
URL: "https://api.example.com",
Description: "Production server",
},
{
URL: "https://staging-api.example.com",
Description: "Staging server",
},
},
},
})Advanced Configuration
openAPIConfig := forge.OpenAPIConfig{
// Basic info
Enabled: true,
Title: "My API",
Version: "1.0.0",
Description: "A comprehensive API for managing resources",
// Contact information
Contact: forge.OpenAPIContact{
Name: "API Support",
Email: "support@example.com",
URL: "https://example.com/support",
},
// License information
License: forge.OpenAPILicense{
Name: "MIT",
URL: "https://opensource.org/licenses/MIT",
},
// Servers
Servers: []forge.OpenAPIServer{
{
URL: "https://api.example.com",
Description: "Production server",
},
},
// Security schemes
SecuritySchemes: map[string]forge.SecurityScheme{
"BearerAuth": {
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
},
"ApiKeyAuth": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
},
// Custom settings
CustomSettings: map[string]interface{}{
"enable_validation": true,
"strict_mode": false,
"generate_examples": true,
},
}Testing OpenAPI
OpenAPI Specification Testing
func TestOpenAPISpec(t *testing.T) {
app := forge.NewTestApp(forge.TestAppConfig{
Name: "test-app",
})
// Register routes
app.Router().GET("/users", func(ctx forge.Context) error {
return ctx.JSON(200, []UserResponse{})
})
app.Router().POST("/users", func(ctx forge.Context) error {
return ctx.JSON(201, UserResponse{})
})
// Generate OpenAPI spec
spec := app.Router().GenerateOpenAPISpec()
// Validate spec
assert.NotNil(t, spec)
assert.Equal(t, "test-app", spec.Info.Title)
assert.Contains(t, spec.Paths, "/users")
// Check paths
usersPath := spec.Paths["/users"]
assert.NotNil(t, usersPath.Get)
assert.NotNil(t, usersPath.Post)
}Documentation Endpoint Testing
func TestOpenAPIEndpoint(t *testing.T) {
app := forge.NewTestApp(forge.TestAppConfig{
Name: "test-app",
})
// Start application
go func() {
app.Run()
}()
defer app.Stop(context.Background())
// Test OpenAPI endpoint
resp, err := http.Get("http://localhost:8080/_/openapi.json")
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
// Parse OpenAPI spec
var spec map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&spec)
require.NoError(t, err)
assert.Equal(t, "test-app", spec["info"].(map[string]interface{})["title"])
}Best Practices
- Schema Design: Design clear, consistent schemas
- Documentation: Provide comprehensive descriptions
- Examples: Include realistic examples
- Validation: Use schema validation for requests/responses
- Security: Document authentication and authorization
- Versioning: Use proper API versioning
- Testing: Test OpenAPI generation and validation
- Standards: Follow OpenAPI 3.0 standards
For more information about API documentation and validation, see the Routing documentation.
How is this guide?
Last updated on