lib/outcome provides a fluent, type-safe API for building HTTP responses. It is the standard way to return data from EVO handlers.
import "github.com/getevo/evo/v2/lib/outcome"Return an outcome value from any handler function:
evo.Get("/users/:id", func(r *evo.Request) any {
user, err := getUserByID(r.Params("id"))
if err != nil {
return outcome.NotFound("user not found")
}
return outcome.OK(user)
})| Function | Status | Default body |
|---|---|---|
OK(data...) |
200 | "OK" |
Created(data...) |
201 | "Created" |
Accepted(data...) |
202 | "Accepted" |
NoContent(data...) |
204 | (empty) |
// 200 with JSON body
return outcome.OK(map[string]any{"id": 1, "name": "Alice"})
// 200 plain text
return outcome.OK("operation complete")
// 201 with created resource
return outcome.Created(newUser)
// 204 no body
return outcome.NoContent()| Function | Status |
|---|---|
BadRequest(data...) |
400 |
Unauthorized(data...) |
401 |
Forbidden(data...) |
403 |
NotFound(data...) |
404 |
NotAcceptable(data...) |
406 |
RequestTimeout(data...) |
408 |
Conflict(data...) |
409 |
UnprocessableEntity(data...) |
422 |
TooManyRequests(data...) |
429 |
UnavailableForLegalReasons(data...) |
451 |
return outcome.BadRequest("invalid input")
return outcome.Unauthorized("token expired")
return outcome.Forbidden("insufficient permissions")
return outcome.NotFound(map[string]string{"error": "user not found"})
return outcome.Conflict("duplicate email")
return outcome.UnprocessableEntity(validationErrors)
return outcome.TooManyRequests("slow down")| Function | Status |
|---|---|
InternalServerError(data...) |
500 |
ServiceUnavailable(data...) |
503 |
GatewayTimeout(data...) |
504 |
return outcome.InternalServerError("unexpected error")
return outcome.ServiceUnavailable("maintenance mode")
return outcome.GatewayTimeout("upstream timed out")return outcome.Text("plain text response") // text/plain
return outcome.Html("<h1>Hello</h1>") // text/html
return outcome.Json(myStruct) // application/jsonreturn outcome.Redirect("/new-path") // 307 Temporary
return outcome.RedirectTemporary("/new-path") // 307
return outcome.RedirectPermanent("/canonical") // 301
return outcome.Redirect("/login", fiber.StatusFound) // custom codeAll constructors return *Response. Chain methods to customize:
Override the status code:
return outcome.OK(data).Status(206) // 206 Partial ContentAdd or replace a response header:
return outcome.OK(data).
Header("X-Request-ID", requestID).
Header("X-API-Version", "v2")Replace the response body. Strings and []byte are sent as-is; structs/maps/slices are JSON-encoded.
return outcome.OK().Content(myStruct)Add a cookie. Complex values (maps, structs, slices) are JSON+base64 encoded.
// Simple string
return outcome.OK(data).Cookie("session", sessionToken)
// With expiry
return outcome.OK(data).Cookie("session", token, 24*time.Hour)
// With expiry time
return outcome.OK(data).Cookie("session", token, time.Now().Add(24*time.Hour))
// Complex value (auto JSON+base64 encoded)
return outcome.OK(data).Cookie("prefs", userPreferences, 30*24*time.Hour)Add a fully configured cookie:
return outcome.OK(data).RawCookie(outcome.Cookie{
Name: "session",
Value: token,
Path: "/",
Secure: true,
HTTPOnly: true,
SameSite: "Strict",
Expires: time.Now().Add(24 * time.Hour),
})Add a redirect (can be chained after setting other properties):
return outcome.OK().Redirect("/dashboard")
return outcome.OK().Redirect("/dashboard", 302)Add an error message and set status (default 400):
return outcome.OK().Error("validation failed", 422)Set Cache-Control header:
// Cache for 1 hour
return outcome.OK(data).SetCacheControl(time.Hour)
// Public cache with revalidation
return outcome.OK(data).SetCacheControl(5*time.Minute, "public", "must-revalidate")
// No caching
return outcome.OK(data).SetCacheControl(0, "no-store")Set Content-Disposition: attachment for file downloads:
return outcome.OK(fileBytes).
Header("Content-Type", "application/pdf").
Filename("report.pdf")Set Content-Disposition: inline to display in the browser:
return outcome.OK(imageBytes).
Header("Content-Type", "image/png").
ShowInBrowser()evo.Get("/api/users", func(r *evo.Request) any {
var users []User
if err := db.Find(&users).Error; err != nil {
return outcome.InternalServerError("failed to load users")
}
return outcome.OK(users)
})evo.Post("/api/users", func(r *evo.Request) any {
var input CreateUserInput
if err := r.BodyParser(&input); err != nil {
return outcome.BadRequest("invalid request body")
}
if errs := validation.Struct(input); len(errs) > 0 {
return outcome.UnprocessableEntity(errs)
}
user, err := createUser(input)
if err != nil {
return outcome.InternalServerError(err.Error())
}
return outcome.Created(user).
Header("Location", "/api/users/"+strconv.Itoa(user.ID))
})evo.Post("/auth/login", func(r *evo.Request) any {
token, err := authenticate(r.FormValue("email"), r.FormValue("password"))
if err != nil {
return outcome.Unauthorized("invalid credentials")
}
return outcome.OK(map[string]string{"token": token}).
Cookie("session", token, 24*time.Hour).
Header("X-Auth-Token", token)
})evo.Get("/reports/:id/pdf", func(r *evo.Request) any {
data, err := generatePDF(r.Params("id"))
if err != nil {
return outcome.NotFound("report not found")
}
return outcome.OK(data).
Header("Content-Type", "application/pdf").
Filename("report-"+r.Params("id")+".pdf").
SetCacheControl(10 * time.Minute)
})evo.Get("/api/articles", func(r *evo.Request) any {
page := r.QueryInt("page", 1)
limit := r.QueryInt("limit", 20)
var articles []Article
var total int64
db.Model(&Article{}).Count(&total)
db.Offset((page - 1) * limit).Limit(limit).Find(&articles)
return outcome.OK(map[string]any{
"data": articles,
"total": total,
"page": page,
"limit": limit,
})
})evo.Post("/auth/logout", func(r *evo.Request) any {
return outcome.RedirectTemporary("/login").
Cookie("session", "", time.Now().Add(-time.Hour)) // expire cookie
})Implement HTTPSerializer to make your own type returnable from handlers:
type HTTPSerializer interface {
GetResponse() Response
}
// Example: custom API response wrapper
type APIResponse struct {
Success bool `json:"success"`
Data any `json:"data,omitempty"`
Message string `json:"message,omitempty"`
}
func (a APIResponse) GetResponse() outcome.Response {
code := 200
if !a.Success {
code = 400
}
data, _ := json.Marshal(a)
return outcome.Response{
StatusCode: code,
ContentType: "application/json",
Data: data,
}
}
// Use in handler
evo.Get("/api/data", func(r *evo.Request) any {
return APIResponse{Success: true, Data: result}
})type Cookie struct {
Name string `json:"name"`
Value string `json:"value"`
Path string `json:"path"`
Domain string `json:"domain"`
Expires time.Time `json:"expires"`
Secure bool `json:"secure"`
HTTPOnly bool `json:"http_only"`
SameSite string `json:"same_site"` // Strict | Lax | None
}type Response struct {
ContentType string
Data interface{} // []byte, string, or any (auto JSON-marshaled)
StatusCode int
Headers map[string]string
RedirectURL string
Cookies []*Cookie
Errors []string
}Access raw data:
resp := outcome.OK(myData)
bytes := resp.GetData() // []byte