Core Concepts
Rocco is built around a few core primitives: Engine, Handler, Request, and Response. Understanding these concepts enables you to build any HTTP API.
Engine
The Engine is rocco's HTTP server. It manages handler registration, middleware, and request routing.
// Create an engine
engine := rocco.NewEngine().WithAuthenticator(extractIdentity)
// Add global middleware
engine.WithMiddleware(loggingMiddleware)
// Register handlers
engine.WithHandlers(handler1, handler2)
// Start serving (HostAll = "", HostLocal = "localhost", HostLoopback = "127.0.0.1")
engine.Start(rocco.HostAll, 8080)
| Method | Description |
|---|---|
NewEngine() | Create engine |
WithAuthenticator(extractor) | Configure identity extraction for authentication |
WithMiddleware(mw...) | Add global middleware |
WithHandlers(handlers...) | Register handlers |
WithModels(models...) | Register standalone types for OpenAPI schemas |
WithSpec(spec) | Configure OpenAPI metadata |
Router() | Access underlying stdlib ServeMux |
Start(host, port) | Begin serving requests |
Shutdown(ctx) | Graceful shutdown |
Handler
A Handler is a typed request processor. It declares input/output types and the function that processes requests.
handler := rocco.NewHandler[InputType, OutputType](
"handler-name", // Unique name for logging/docs
"POST", // HTTP method
"/path", // URL path
func(req *rocco.Request[InputType]) (OutputType, error) {
// Process request, return response or error
return OutputType{...}, nil
},
)
Handler Configuration
Handlers use a builder pattern for configuration:
handler := rocco.NewHandler[CreateOrderInput, OrderOutput](
"create-order",
"POST",
"/orders",
handleCreateOrder,
).
WithSummary("Create a new order"). // OpenAPI summary
WithDescription("Creates an order..."). // OpenAPI description
WithTags("orders"). // OpenAPI tags
WithSuccessStatus(201). // Success status code
WithPathParams("id"). // Declare path params
WithQueryParams("include"). // Declare query params
WithErrors(ErrNotFound, ErrConflict). // Declare possible errors
WithAuthentication(). // Require authentication
WithScopes("orders:write"). // Require scope
WithRoles("admin", "manager"). // Require role
WithMaxBodySize(1024 * 1024). // 1MB body limit
WithMiddleware(customMiddleware) // Handler-specific middleware
NoBody Type
For handlers without request bodies (GET, DELETE), use rocco.NoBody:
handler := rocco.NewHandler[rocco.NoBody, UserOutput](
"get-user",
"GET",
"/users/{id}",
func(req *rocco.Request[rocco.NoBody]) (UserOutput, error) {
// req.Body is NoBody (empty struct)
return UserOutput{...}, nil
},
)
Request
The Request struct provides access to the parsed body, parameters, identity, and underlying HTTP request.
type Request[T any] struct {
Context context.Context // Request context
Request *http.Request // Underlying HTTP request
Params *Params // Path and query parameters
Body T // Parsed and validated body
Identity Identity // Authenticated identity (if any)
}
Accessing Parameters
func(req *rocco.Request[MyInput]) (MyOutput, error) {
// Path parameters
userID := req.Params.Path["id"]
// Query parameters
page := req.Params.Query["page"]
filter := req.Params.Query["filter"]
// Request body (already parsed and validated)
name := req.Body.Name
// Identity (if authenticated)
if req.Identity != nil {
tenantID := req.Identity.TenantID()
}
// Underlying request
header := req.Request.Header.Get("X-Custom")
return MyOutput{...}, nil
}
Parameter Declaration
Handlers must declare their parameters:
// Path parameters use {param} syntax
handler := rocco.NewHandler[rocco.NoBody, Output](
"get-item",
"GET",
"/items/{category}/{id}",
handleGetItem,
).WithPathParams("category", "id")
// Query parameters
handler := rocco.NewHandler[rocco.NoBody, Output](
"search",
"GET",
"/search",
handleSearch,
).WithQueryParams("q", "page", "limit", "sort")
Response
Handlers return a typed output value and an error. Rocco handles serialization automatically.
Success Responses
func(req *rocco.Request[Input]) (Output, error) {
// Return output - automatically JSON-encoded
return Output{
ID: "123",
Name: req.Body.Name,
}, nil
}
Default success status is 200 OK. Override with WithSuccessStatus():
handler.WithSuccessStatus(201) // 201 Created
handler.WithSuccessStatus(204) // 204 No Content
Error Responses
Return errors to send error responses:
func(req *rocco.Request[Input]) (Output, error) {
user, err := db.FindUser(req.Params.Path["id"])
if err != nil {
// Sentinel error - returns structured JSON response
return Output{}, rocco.ErrNotFound.WithMessage("user not found")
}
return Output{...}, nil
}
Errors must be declared with WithErrors():
handler.WithErrors(rocco.ErrNotFound, rocco.ErrConflict)
Validation
Validation is opt-in via the Validatable interface. Types that implement this interface are automatically validated.
Validatable Interface
type Validatable interface {
Validate() error
}
Adding Validation to Types
Implement Validate() on your input or output types. Rocco integrates with the check package for clean, composable validation:
import "github.com/zoobz-io/check"
type CreateUserInput struct {
Name string `json:"name"`
Email string `json:"email"`
}
func (c CreateUserInput) Validate() error {
return check.All(
check.Required(c.Name, "name"),
check.Email(c.Email, "email"),
)
}
When a handler uses CreateUserInput, validation runs automatically after JSON parsing. No additional configuration needed.
Using check Validators
The check package provides many built-in validators:
import "github.com/zoobz-io/check"
type CreateUserInput struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
Website string `json:"website,omitempty"`
}
func (c CreateUserInput) Validate() error {
return check.All(
check.Required(c.Name, "name"),
check.MinLength(c.Name, 2, "name"),
check.MaxLength(c.Name, 100, "name"),
check.Required(c.Email, "email"),
check.Email(c.Email, "email"),
check.Min(c.Age, 0, "age"),
check.Max(c.Age, 150, "age"),
check.URL(c.Website, "website"), // optional field, only validated if non-empty
)
}
Output Validation
Output validation is opt-in for performance. Enable with WithOutputValidation():
handler := rocco.POST[Input, Output]("/path", fn).
WithOutputValidation() // Validates output if Output implements Validatable
Validation Tags for OpenAPI
The validate struct tag is still parsed for OpenAPI schema generation, even without runtime validation:
type CreateUserInput struct {
Name string `json:"name" validate:"required,min=2,max=100"`
Email string `json:"email" validate:"required,email"`
}
This generates OpenAPI constraints (minLength, maxLength, format: email) in your spec.
Validation Errors
Invalid inputs return 422 Unprocessable Entity with detailed field errors:
{
"code": "VALIDATION_FAILED",
"message": "validation failed",
"details": {
"fields": [
{"field": "email", "message": "must be a valid email address"}
]
}
}
Identity
Identity represents an authenticated user or service. Implement the Identity interface:
type Identity interface {
ID() string // Unique identifier
TenantID() string // Tenant/organization ID
Scopes() []string // Permission scopes
Roles() []string // User roles
Stats() map[string]int // Usage statistics
HasScope(string) bool // Check scope
HasRole(string) bool // Check role
}
Configure identity extraction via WithAuthenticator:
engine := rocco.NewEngine().WithAuthenticator(func(ctx context.Context, r *http.Request) (rocco.Identity, error) {
token := r.Header.Get("Authorization")
// Validate token, return identity or error
return &MyIdentity{...}, nil
})
Next Steps
- Architecture - How rocco processes requests internally
- Handler Guide - Advanced handler patterns
- Error Handling - Error patterns and custom errors
See Also
- Authentication Guide - Identity and authorization
- API Reference - Complete API documentation