# Outcome — HTTP Response Library `lib/outcome` provides a fluent, type-safe API for building HTTP responses. It is the standard way to return data from EVO handlers. ## Import ```go import "github.com/getevo/evo/v2/lib/outcome" ``` ## Basic usage Return an outcome value from any handler function: ```go 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) }) ``` ## Response constructors ### 2xx — Success | Function | Status | Default body | |---|---|---| | `OK(data...)` | 200 | `"OK"` | | `Created(data...)` | 201 | `"Created"` | | `Accepted(data...)` | 202 | `"Accepted"` | | `NoContent(data...)` | 204 | _(empty)_ | ```go // 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() ``` ### 4xx — Client errors | 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 | ```go 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") ``` ### 5xx — Server errors | Function | Status | |---|---| | `InternalServerError(data...)` | 500 | | `ServiceUnavailable(data...)` | 503 | | `GatewayTimeout(data...)` | 504 | ```go return outcome.InternalServerError("unexpected error") return outcome.ServiceUnavailable("maintenance mode") return outcome.GatewayTimeout("upstream timed out") ``` ### Content type helpers ```go return outcome.Text("plain text response") // text/plain return outcome.Html("

Hello

") // text/html return outcome.Json(myStruct) // application/json ``` ### Redirects ```go return outcome.Redirect("/new-path") // 307 Temporary return outcome.RedirectTemporary("/new-path") // 307 return outcome.RedirectPermanent("/canonical") // 301 return outcome.Redirect("/login", fiber.StatusFound) // custom code ``` ## Builder methods All constructors return `*Response`. Chain methods to customize: ### `.Status(code int)` Override the status code: ```go return outcome.OK(data).Status(206) // 206 Partial Content ``` ### `.Header(key, value string)` Add or replace a response header: ```go return outcome.OK(data). Header("X-Request-ID", requestID). Header("X-API-Version", "v2") ``` ### `.Content(input any)` Replace the response body. Strings and `[]byte` are sent as-is; structs/maps/slices are JSON-encoded. ```go return outcome.OK().Content(myStruct) ``` ### `.Cookie(key, value, params...)` Add a cookie. Complex values (maps, structs, slices) are JSON+base64 encoded. ```go // 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) ``` ### `.RawCookie(cookie Cookie)` Add a fully configured cookie: ```go 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), }) ``` ### `.Redirect(to, code...)` Add a redirect (can be chained after setting other properties): ```go return outcome.OK().Redirect("/dashboard") return outcome.OK().Redirect("/dashboard", 302) ``` ### `.Error(value, code...)` Add an error message and set status (default 400): ```go return outcome.OK().Error("validation failed", 422) ``` ### `.SetCacheControl(duration, directives...)` Set `Cache-Control` header: ```go // 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") ``` ### `.Filename(name string)` Set `Content-Disposition: attachment` for file downloads: ```go return outcome.OK(fileBytes). Header("Content-Type", "application/pdf"). Filename("report.pdf") ``` ### `.ShowInBrowser()` Set `Content-Disposition: inline` to display in the browser: ```go return outcome.OK(imageBytes). Header("Content-Type", "image/png"). ShowInBrowser() ``` ## Handler examples ### JSON API endpoint ```go 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) }) ``` ### Create resource ```go 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)) }) ``` ### Authentication ```go 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) }) ``` ### File download ```go 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) }) ``` ### Paginated list ```go 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, }) }) ``` ### Redirect with cookie cleanup ```go evo.Post("/auth/logout", func(r *evo.Request) any { return outcome.RedirectTemporary("/login"). Cookie("session", "", time.Now().Add(-time.Hour)) // expire cookie }) ``` ## `HTTPSerializer` interface Implement `HTTPSerializer` to make your own type returnable from handlers: ```go 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} }) ``` ## `Cookie` type ```go 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 } ``` ## `Response` struct ```go 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: ```go resp := outcome.OK(myData) bytes := resp.GetData() // []byte ``` ## See Also - [Web Server](webserver.md) - [Validation](validation.md) - [Errors](../lib/errors/README.md)