Route Configuration
Configure routes with options for metadata, auth, validation, and schemas
Every route in Forge can be configured with options that control metadata, authentication, validation, schema generation, and more. Route options are passed as variadic arguments when registering a route.
app.POST("/users", createUser,
forge.WithName("create-user"),
forge.WithTags("users"),
forge.WithDescription("Create a new user account"),
forge.WithTimeout(10 * time.Second),
forge.WithAuth("jwt"),
forge.WithValidation(true),
)Metadata Options
These options attach descriptive metadata to routes, used for documentation generation, route inspection, and tooling.
WithName
Assigns a unique name to the route for lookup with RouteByName.
app.GET("/users/:id", getUser, forge.WithName("get-user"))
// Later, find the route by name
route, found := app.RouteByName("get-user")WithSummary and WithDescription
Set short and long descriptions. These appear in OpenAPI documentation.
app.GET("/users", listUsers,
forge.WithSummary("List all users"),
forge.WithDescription("Returns a paginated list of users. Supports filtering by role and status."),
)WithTags
Organize routes into logical groups in the API documentation. Tags also enable lookup via RoutesByTag.
app.GET("/users", listUsers, forge.WithTags("users", "admin"))
app.POST("/users", createUser, forge.WithTags("users"))
// Find all user routes
userRoutes := app.RoutesByTag("users")WithMetadata
Attach arbitrary key-value pairs to a route. Metadata can be used by interceptors, middleware, or custom tooling.
app.DELETE("/users/:id", deleteUser,
forge.WithMetadata("role", "admin"),
forge.WithMetadata("audit", true),
)
// Query routes by metadata
adminRoutes := app.RoutesByMetadata("role", "admin")WithOperationID
Set an explicit OpenAPI operation ID. If not provided, one is generated from the method and path.
app.GET("/users/:id", getUser, forge.WithOperationID("getUserById"))WithDeprecated
Mark a route as deprecated in OpenAPI documentation.
app.GET("/v1/users", listUsersV1,
forge.WithDeprecated(),
forge.WithDescription("Use /v2/users instead"),
)WithTimeout
Set a per-route timeout. If the handler does not complete within this duration, the context is cancelled.
app.POST("/reports/generate", generateReport,
forge.WithTimeout(30 * time.Second),
)WithMiddleware
Attach middleware to a specific route, in addition to any group or global middleware.
app.POST("/upload", uploadHandler,
forge.WithMiddleware(middleware.RateLimit(limiter, logger)),
)Authentication Options
Forge provides declarative authentication configuration at the route level. These options work with the auth extension to enforce authentication requirements.
WithAuth
Require authentication using one or more providers. Multiple providers create an OR condition -- any single provider succeeding grants access.
// Accept either JWT or API key
app.GET("/protected", handler,
forge.WithAuth("jwt", "api-key"),
)WithRequiredAuth
Require a specific authentication provider AND specific scopes or permissions.
// Require JWT auth with write:users and admin scopes
app.POST("/admin/users", createAdmin,
forge.WithRequiredAuth("jwt", "write:users", "admin"),
)WithAuthAnd
Require ALL specified providers to succeed. This creates an AND condition, useful for multi-factor authentication.
// Require both API key AND MFA token
app.POST("/transfer", transferFunds,
forge.WithAuthAnd("api-key", "mfa"),
)Validation Options
WithValidation
Enable or disable request validation for a route.
app.POST("/users", createUser,
forge.WithValidation(true),
forge.WithRequestSchema(&CreateUserRequest{}),
)WithStrictValidation
Enable strict validation that validates both the request and the response against their schemas.
app.POST("/users", createUser,
forge.WithStrictValidation(),
forge.WithRequestSchema(&CreateUserRequest{}),
forge.WithResponseSchema(200, "Created user", &UserResponse{}),
)Schema Options
These options control how routes appear in OpenAPI, AsyncAPI, and oRPC documentation.
WithRequestSchema
The recommended approach for defining request schemas. Struct fields are classified automatically based on their tags.
type CreateUserRequest struct {
TenantID string `path:"tenantId" description:"Tenant ID" format:"uuid"`
DryRun bool `query:"dryRun" description:"Preview mode"`
APIKey string `header:"X-API-Key" description:"API key"`
Name string `json:"name" body:"" description:"User name" minLength:"1"`
Email string `json:"email" body:"" description:"Email" format:"email"`
}
app.POST("/tenants/:tenantId/users", createUser,
forge.WithRequestSchema(&CreateUserRequest{}),
)If the struct has no path, query, or header tags, it is treated as a body-only schema for backward compatibility.
WithRequestBodySchema
Set only the request body schema explicitly. Use this when you need separate schemas for body versus parameters.
type CreateUserBody struct {
Name string `json:"name"`
Email string `json:"email"`
}
app.POST("/users", createUser,
forge.WithRequestBodySchema(&CreateUserBody{}),
)WithResponseSchema
Define response schemas for specific HTTP status codes.
app.GET("/users/:id", getUser,
forge.WithResponseSchema(200, "User found", &UserResponse{}),
forge.WithResponseSchema(404, "User not found", &ErrorResponse{}),
)WithQuerySchema and WithHeaderSchema
Define schemas for query parameters and headers separately.
type ListUsersQuery struct {
Page int `query:"page" minimum:"1"`
Limit int `query:"limit" minimum:"1" maximum:"100"`
Sort string `query:"sort" enum:"name,created_at"`
}
type ListUsersHeaders struct {
AcceptLanguage string `header:"Accept-Language"`
}
app.GET("/users", listUsers,
forge.WithQuerySchema(&ListUsersQuery{}),
forge.WithHeaderSchema(&ListUsersHeaders{}),
)WithRequestContentTypes and WithResponseContentTypes
Override the default content types for request and response bodies.
app.POST("/upload", uploadHandler,
forge.WithRequestContentTypes("multipart/form-data"),
forge.WithResponseContentTypes("application/json", "text/plain"),
)Schema Exclusion Options
Control which routes appear in generated API documentation.
WithOpenAPIExclude
Exclude a route from OpenAPI schema generation.
app.GET("/internal/health", healthHandler,
forge.WithOpenAPIExclude(),
)WithAsyncAPIExclude
Exclude a streaming route from AsyncAPI schema generation.
app.WebSocket("/internal/debug-stream", debugHandler,
forge.WithAsyncAPIExclude(),
)WithORPCExclude
Exclude a route from oRPC auto-exposure.
app.GET("/internal/debug", debugHandler,
forge.WithORPCExclude(),
)WithSchemaExclude
Exclude a route from all schema generation (OpenAPI, AsyncAPI, and oRPC) at once.
app.GET("/internal/status", statusHandler,
forge.WithSchemaExclude(),
)Method Override
WithMethod
Override the HTTP method for a route. Primarily used for SSE and WebSocket endpoints that default to GET.
// SSE endpoint that accepts POST requests with a body
app.SSE("/events", handler, forge.WithMethod(http.MethodPost))
// EventStream with POST
app.EventStream("/stream", streamHandler,
forge.WithMethod(http.MethodPost),
forge.WithTags("streaming"),
)Sensitive Field Cleaning
WithSensitiveFieldCleaning
Enable automatic cleaning of fields marked with the sensitive struct tag before JSON serialization. See Sensitive Fields for details.
app.GET("/users/:id", getUser,
forge.WithSensitiveFieldCleaning(),
)RouteConfig Struct
Internally, all route options are applied to a RouteConfig struct. Understanding this struct helps when building custom route options.
type RouteConfig struct {
Name string
Method string
Summary string
Description string
Tags []string
Middleware []Middleware
Timeout time.Duration
Metadata map[string]any
Interceptors []Interceptor
SkipInterceptors map[string]bool
OperationID string
Deprecated bool
SensitiveFieldCleaning bool
}RouteInfo Inspection
After registration, routes can be inspected using the RouteInfo struct returned by Routes(), RouteByName(), RoutesByTag(), and RoutesByMetadata().
routes := app.Routes()
for _, r := range routes {
fmt.Printf("[%s] %s %s\n", r.Name, r.Method, r.Path)
fmt.Printf(" Tags: %v\n", r.Tags)
fmt.Printf(" Summary: %s\n", r.Summary)
if r.Metadata != nil {
if role, ok := r.Metadata["role"]; ok {
fmt.Printf(" Required role: %v\n", role)
}
}
}Complete Example
Here is a route with comprehensive configuration demonstrating many options together.
app.POST("/api/v1/tenants/:tenantId/users", createUser,
forge.WithName("create-tenant-user"),
forge.WithOperationID("createTenantUser"),
forge.WithSummary("Create a user within a tenant"),
forge.WithDescription("Creates a new user account under the specified tenant. Requires admin scope."),
forge.WithTags("users", "tenants"),
forge.WithTimeout(10 * time.Second),
forge.WithAuth("jwt"),
forge.WithRequiredAuth("jwt", "admin", "write:users"),
forge.WithStrictValidation(),
forge.WithRequestSchema(&CreateTenantUserRequest{}),
forge.WithResponseSchema(201, "User created", &UserResponse{}),
forge.WithResponseSchema(409, "User already exists", &ErrorResponse{}),
forge.WithMetadata("audit", true),
forge.WithSensitiveFieldCleaning(),
forge.WithInterceptor(RequireAdmin),
)How is this guide?