Example 04: Handler Forms
Explore 4 essential handler patterns
Time: 10 minutes โข Concepts: Handler signatures, when to use each form
๐ฏ What Youโll Learn
- 4 essential handler forms (out of 29 total!)
- When to use each form
- Request binding patterns
- Context access
- Custom responses
๐ Run It
cd docs/01-router-guide/01-router/examples/04-handler-forms
go run main.go
Server starts on: http://localhost:3000
๐งช Test It
Form 1: Simple Return Value
curl http://localhost:3000/ping
Response:
"pong"
curl http://localhost:3000/time
Response:
{
"current_time": "2025-10-22T10:30:00Z"
}
Form 2: Return with Error (Most Common!)
curl http://localhost:3000/users
Response:
[
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]
Form 3: Request Binding with Error
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'
Response:
{
"id": 3,
"name": "Charlie",
"email": "charlie@example.com"
}
curl http://localhost:3000/users/1
Response:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
Form 4: Context + Request with Error
curl http://localhost:3000/users/1/details -H "User-Agent: MyApp/1.0"
Server logs:
Request from: MyApp/1.0
Response:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
Form 5: Custom Response (Full Control)
curl -i http://localhost:3000/users/1/custom
Response (with custom headers):
HTTP/1.1 200 OK
Content-Type: application/json
X-User-ID: 1
X-Response-Time: 2025-10-22T10:30:00Z
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
Error response:
curl -i http://localhost:3000/users/999/custom
HTTP/1.1 404 Not Found
X-Error-Code: USR404
{
"error": {
"code": "USER_NOT_FOUND",
"message": "User does not exist"
}
}
๐ Handler Forms Explained
Form 1: Simple Return Value
Signature: func() T
When to use:
- โ Simple data, no errors possible
- โ Static responses
- โ Health checks, ping endpoints
Example:
r.GET("/ping", func() string {
return "pong"
})
r.GET("/config", func() map[string]string {
return map[string]string{
"version": "1.0",
"env": "production",
}
})
Form 2: Return with Error (90% of Cases!)
Signature: func() (T, error)
When to use:
- โ Database queries
- โ File operations
- โ External API calls
- โ Any operation that can fail
Example:
r.GET("/users", func() ([]User, error) {
users, err := db.GetUsers()
if err != nil {
return nil, err // Lokstra handles error response
}
return users, nil // Auto 200 OK with JSON
})
Why most common?
- Lokstra automatically converts errors to HTTP responses
- Clean code: just return the error
- No need to manually set status codes
Form 3: Request Binding with Error
Signature: func(req *RequestType) (T, error)
When to use:
- โ POST/PUT endpoints (need body data)
- โ Path parameters
- โ Query parameters
- โ Need validation
Example:
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
r.POST("/users", func(req *CreateUserRequest) (*User, error) {
// req is auto-bound from JSON body
// auto-validated
user, err := db.CreateUser(req.Name, req.Email)
return user, err
})
Binding sources:
json:"name"- From JSON bodypath:"id"- From URL pathquery:"page"- From query stringheader:"Authorization"- From headers
Form 4: Context + Request with Error
Signature: func(ctx *request.Context, req *RequestType) (T, error)
When to use:
- โ Need to read headers
- โ Need to access raw request
- โ Logging with request metadata
- โ Custom authentication logic
Example:
r.GET("/users/{id}", func(ctx *request.Context, req *GetUserRequest) (*User, error) {
// Access headers
authToken := ctx.R.Header.Get("Authorization")
userAgent := ctx.R.Header.Get("User-Agent")
// Log request
log.Printf("User %d requested by %s", req.ID, userAgent)
// Still have request binding
user, err := db.GetUser(req.ID)
return user, err
})
What ctx provides:
ctx.R- Raw*http.Requestctx.W- Response writerctx.PathParams- Path parameters mapctx.Query()- Query parameters
Form 5: Custom Response (Full Control)
Signature: func(...) (*response.Response, error)
When to use:
- โ Custom status codes
- โ Custom headers (CORS, caching, etc)
- โ Multiple response types
- โ Fine-grained error responses
Example:
r.GET("/users/{id}", func(ctx *request.Context, req *GetUserRequest) (*response.Response, error) {
user, err := db.GetUser(req.ID)
if err != nil {
// Custom error response
return response.Error(404, "USER_NOT_FOUND", "User does not exist").
WithHeader("X-Error-ID", generateErrorID()), nil
}
// Custom success response
return response.Success(user).
WithHeader("X-User-ID", fmt.Sprintf("%d", user.ID)).
WithHeader("Cache-Control", "max-age=3600").
WithStatus(200), nil
})
๐ Comparison Table
| Form | Parameters | Return | Use Case | % Usage |
|---|---|---|---|---|
| 1 | None | T |
Simple, static | 5% |
| 2 | None | (T, error) |
Can fail | 60% |
| 3 | Request | (T, error) |
Need input | 30% |
| 4 | Context + Request | (T, error) |
Need headers | 4% |
| 5 | Context (optional) | (*Response, error) |
Custom control | 1% |
Recommendation:
- Start with Form 2 or 3 (90% of cases)
- Use Form 4 when you need headers
- Use Form 5 only when necessary
๐ What You Learned
- โ Form 1: Simple return (no errors)
- โ Form 2: Return with error (most common!)
- โ Form 3: Request binding (POST/PUT)
- โ Form 4: Context access (headers)
- โ Form 5: Custom responses (full control)
- โ When to use each form
- โ Automatic JSON conversion
- โ Automatic error handling
๐ก Decision Guide
Do you need request data (body/path/query)?
โโ NO โ Do errors happen?
โ โโ NO โ Form 1 (simple return)
โ โโ YES โ Form 2 (return with error)
โ
โโ YES โ Do you need headers/cookies?
โโ NO โ Form 3 (request binding)
โโ YES โ Do you need custom status/headers?
โโ NO โ Form 4 (context + request)
โโ YES โ Form 5 (custom response)
๐ Additional Forms
Lokstra supports 29 total handler forms!
Other useful forms:
func(ctx *request.Context) error- Context only, no returnfunc(req *Request) error- Request only, no data returnfunc() error- No params, just error- And 21 more variations!
See: Deep Dive: All 29 Handler Forms
Next: Try combining these patterns in a complete application!
Back: 03 - Route Groups