Example 05: Response Patterns
Master all 3 response methods and 2 response paths
Time: 15 minutes β’ Concepts: Response types, paths, when to use each
π― What Youβll Learn
Lokstra provides flexibility in how you send responses:
3 Response Types:
- Manual - Full control using
http.ResponseWriter - Generic - Unopinionated using
response.Response(JSON, HTML, text, etc) - Opinionated - Structured API using
response.ApiHelper(JSON only)
2 Response Paths:
- Via Context - Use
request.Contextto write response - Via Return - Return data or response objects
π Run It
cd docs/01-router-guide/01-router/examples/05-response-patterns
go run main.go
Server starts on: http://localhost:3000
π Understanding Response Types
Type 1: Manual Response (http.ResponseWriter)
Full manual control - You write everything yourself.
r.GET("/manual/json", func(ctx *request.Context) error {
ctx.W.Header().Set("Content-Type", "application/json")
ctx.W.Header().Set("X-Custom-Header", "value")
ctx.W.WriteHeader(200)
ctx.W.Write([]byte(`{"message":"Manual response"}`))
return nil
})
When to use:
- β Streaming responses
- β Custom binary formats
- β Need absolute control
- β Performance-critical paths
Pros:
- β Maximum control
- β No framework overhead
- β Can do anything
Cons:
- β Most verbose
- β Easy to make mistakes
- β No structure
Type 2: Generic Response (response.Response)
Unopinionated - Can send JSON, HTML, text, or any format.
// JSON
r.GET("/response/json", func() *response.Response {
resp := response.NewResponse()
resp.RespHeaders = map[string][]string{
"X-Custom": {"value"},
}
resp.WithStatus(200).Json(data)
return resp
})
// HTML
r.GET("/response/html", func() *response.Response {
resp := response.NewResponse()
resp.Html("<html>...</html>")
return resp
})
// Plain Text
r.GET("/response/text", func() *response.Response {
resp := response.NewResponse()
resp.Text("Plain text")
return resp
})
When to use:
- β Mixed content types (JSON, HTML, text)
- β Need custom headers/status
- β Donβt want opinionated structure
- β File downloads, streams
Pros:
- β Flexible (multiple formats)
- β Clean API
- β Custom headers/status easy
Cons:
- β οΈ No standard JSON structure
- β οΈ You define error format
Type 3: Opinionated API (response.ApiHelper)
Structured JSON API - Standard response format enforced.
// Success
r.GET("/api/success", func() *response.ApiHelper {
api := response.NewApiHelper()
api.Ok(data) // Standard success format
return api
})
// Success with message
r.GET("/api/message", func() *response.ApiHelper {
api := response.NewApiHelper()
api.OkWithMessage(data, "Operation successful")
return api
})
// Created (201)
r.POST("/api/created", func() *response.ApiHelper {
api := response.NewApiHelper()
api.Created(newResource, "Resource created")
return api
})
// Error
r.GET("/api/error", func() *response.ApiHelper {
api := response.NewApiHelper()
api.NotFound("Resource not found")
return api
})
Standard Response Format:
Success:
{
"status": "success",
"data": { ... }
}
Success with message:
{
"status": "success",
"message": "Operation successful",
"data": { ... }
}
Error:
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Resource not found"
}
}
When to use:
- β REST APIs (highly recommended!)
- β Consistent JSON structure
- β Multiple clients need same format
- β API documentation
Pros:
- β Consistent structure
- β Easy for clients to parse
- β Clear success/error distinction
- β Standard HTTP status codes
Cons:
- β JSON only (no HTML/text)
- β Opinionated structure
- β Canβt customize format easily
π Understanding Response Paths
Path 1: Via Context (func(ctx *request.Context) error)
Write response using context helpers. Works for all response types.
// Manual - Direct control with ctx.W
r.GET("/manual/json", func(ctx *request.Context) error {
ctx.W.Header().Set("Content-Type", "application/json")
ctx.W.Write([]byte(`{"message":"hello"}`))
return nil
})
// Generic Response - Using ctx.Resp (can return directly!)
r.GET("/ctx-resp/json", func(ctx *request.Context) error {
return ctx.Resp.WithStatus(200).Json(data)
})
r.GET("/ctx-resp/html", func(ctx *request.Context) error {
return ctx.Resp.Html("<html>...</html>")
})
// Opinionated API - Using ctx.Api (can return directly!)
r.GET("/ctx-api/success", func(ctx *request.Context) error {
return ctx.Api.Ok(data)
})
r.GET("/ctx-api/error", func(ctx *request.Context) error {
return ctx.Api.NotFound("Resource not found")
})
Characteristics:
- Must have
*request.Contextparameter - Use
ctx.Wfor manual control (return nil) - Use
ctx.Respfor generic responses - can return directly! - Use
ctx.Apifor opinionated API - can return directly! - Methods return
error, so you canreturn ctx.Api.Ok(data)directly
Path 2: Via Return (func() T or func() (T, error))
Return response object or data. Works for all response types except manual.
// Return plain data (auto JSON)
r.GET("/return/data", func() any {
return map[string]string{"message": "hello"}
})
// Return data with error
r.GET("/return/data-error", func() (any, error) {
return data, nil
})
// Return Response object
r.GET("/return/response", func() *response.Response {
resp := response.NewResponse()
resp.Json(data)
return resp
})
// Return Response with error handling
r.GET("/return/response-error", func() (*response.Response, error) {
resp := response.NewResponse()
resp.Json(data)
return resp, nil
})
// Return ApiHelper object
r.GET("/return/api", func() *response.ApiHelper {
api := response.NewApiHelper()
api.Ok(data)
return api
})
// Return ApiHelper with error handling
r.GET("/return/api-error", func() (*response.ApiHelper, error) {
api := response.NewApiHelper()
users, err := db.GetUsers()
if err != nil {
api.InternalError("Database error")
return api, nil
}
api.Ok(users)
return api, nil
})
Characteristics:
- No
*request.Contextneeded (optional) - Return response or data
- Lokstra handles response writing
- Cleaner for most cases
- Cannot be used for manual
ctx.Wresponses
π Complete Response Matrix
| Response Type | Via Context | Via Return | Use Case |
|---|---|---|---|
| Manual | β
ctx.W.Write() |
β Not supported | Streaming, binary |
| Generic Response | β
ctx.Resp.Json() |
β
return resp or return resp, nil |
Mixed formats (JSON/HTML/text) |
| Opinionated API | β
ctx.Api.Ok() |
β
return api or return api, nil |
REST APIs |
| Plain Data | β Not supported | β
return data |
Simple JSON |
Key Points:
- Manual (
ctx.W): Only via context, cannot be returned - Generic (
response.Response): Can usectx.RespOR return - Opinionated (
response.ApiHelper): Can usectx.ApiOR return - Plain Data: Only via return
π§ͺ Test Examples
Manual Response
curl http://localhost:3000/manual/json
curl http://localhost:3000/manual/text
Response (manual/json):
{"message":"Manual JSON response","method":"http.ResponseWriter"}
Generic Response
curl http://localhost:3000/response/json
curl http://localhost:3000/response/html
curl http://localhost:3000/response/text
curl -i http://localhost:3000/response/custom-status
Response (response/json):
{
"message": "Generic JSON using response.Response",
"data": [...]
}
Opinionated API
curl http://localhost:3000/api/success
curl http://localhost:3000/api/success-message
curl -X POST http://localhost:3000/api/created
curl http://localhost:3000/api/error-notfound
Response (api/success):
{
"status": "success",
"data": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]
}
Response (api/error-notfound):
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "User not found"
}
}
Return Values
curl http://localhost:3000/return/data
curl http://localhost:3000/return/struct
curl http://localhost:3000/return/response
curl http://localhost:3000/return/api
Response (return/data):
{
"message": "Direct data return",
"users": [...],
"count": 2
}
Comparison
# Same data, 4 different methods
curl http://localhost:3000/compare/manual
curl http://localhost:3000/compare/response
curl http://localhost:3000/compare/api
curl http://localhost:3000/compare/return
π― Decision Guide
Choose Response Type:
Need HTML/text/binary?
ββ YES β Use response.Response (generic)
β
ββ NO (JSON only)
ββ Need consistent API structure?
β ββ YES β Use response.ApiHelper β (recommended for APIs)
β ββ NO β Use response.Response or plain return
β
ββ Need absolute control?
ββ YES β Use manual (http.ResponseWriter)
Choose Response Path:
Simple data return?
ββ YES β Use return path (func() T)
β
ββ NO
ββ Need to read request headers/body?
β ββ YES β Use context path (func(ctx))
β
ββ Need error handling?
ββ YES β Use return with error (func() (T, error))
π‘ Best Practices
1. For REST APIs: Use ApiHelper
// β
Recommended for APIs
r.GET("/users", func() (*response.ApiHelper, error) {
api := response.NewApiHelper()
users, err := db.GetUsers()
if err != nil {
api.InternalError("Database error")
return api, nil
}
api.Ok(users)
return api, nil
})
Why?
- Consistent structure
- Client-friendly
- Clear error format
- Standard HTTP codes
2. For Simple Data: Use Return
// β
Simplest for basic data
r.GET("/stats", func() any {
return map[string]int{
"users": 100,
"posts": 500,
}
})
3. For Mixed Content: Use response.Response
// β
When you need HTML, JSON, text, etc
r.GET("/page", func() (*response.Response, error) {
resp := response.NewResponse()
if acceptsJSON(req) {
resp.Json(data)
} else {
resp.Html(htmlPage)
}
return resp, nil
})
4. For Streaming: Use Manual
// β
For SSE, file streaming, etc
r.GET("/stream", func(ctx *request.Context) error {
ctx.W.Header().Set("Content-Type", "text/event-stream")
for event := range events {
fmt.Fprintf(ctx.W, "data: %s\n\n", event)
ctx.W.(http.Flusher).Flush()
}
return nil
})
π ApiHelper Methods Reference
Success Methods
api.Ok(data) // 200 OK
api.OkWithMessage(data, "message") // 200 OK with message
api.Created(data, "message") // 201 Created
api.OkList(data, meta) // 200 OK with pagination
Error Methods
api.BadRequest(code, message) // 400 Bad Request
api.Unauthorized(message) // 401 Unauthorized
api.Forbidden(message) // 403 Forbidden
api.NotFound(message) // 404 Not Found
api.InternalError(message) // 500 Internal Server Error
π What You Learned
- β 3 response types: Manual, Generic, Opinionated
- β 2 response paths: Via Context, Via Return
- β When to use each type
- β ApiHelper standard format
- β response.Response flexibility
- β Manual control with http.ResponseWriter
- β Decision guide for choosing
π Related
- ApiHelper Guide: Deep Dive (coming soon)
- Response Formats: Deep Dive (coming soon)
- Error Handling: Guide (coming soon)
Back: 04 - Handler Forms
Next: Ready to build a complete API!