Response Handling
Okapi provides a rich set of response methods to send various types of responses back to the client with appropriate HTTP status codes and content types.
Quick Start
The simplest way to send a response is using convenience methods:
o.Get("/books/:id", func(c *okapi.Context) error {
book := Book{
ID: 1,
Name: "The Great Go Book",
Price: 20,
}
return c.OK(book) // Sends 200 OK with JSON
})
Response Methods
JSON Responses
Send JSON data with a specific status code:
o.Get("/books", func(c *okapi.Context) error {
books := []Book{{ID: 1, Name: "Go Programming"}}
return c.JSON(http.StatusOK, books)
})
Text Responses
Send plain text:
o.Get("/hello", func(c *okapi.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
HTML Responses
c.HTML(code, file, data) parses an HTML template file and executes it, and
c.HTMLView(code, templateString, data) does the same for a template held in a
string. Both escape interpolated values through html/template.
o.Get("/page", func(c *okapi.Context) error {
return c.HTML(http.StatusOK, "views/page.html", okapi.M{"title": "Welcome"})
})
o.Get("/inline", func(c *okapi.Context) error {
return c.HTMLView(http.StatusOK, "<h1>{{.title}}</h1>", okapi.M{"title": "Welcome"})
})
To send HTML you already hold as bytes, without templating, use c.Data:
o.Get("/raw", func(c *okapi.Context) error {
return c.Data(http.StatusOK, "text/html", []byte("<h1>Welcome</h1>"))
})
XML Responses
Send XML data:
o.Get("/books", func(c *okapi.Context) error {
books := []Book{{ID: 1, Name: "Go Programming"}}
return c.XML(http.StatusOK, books)
})
File Responses
Serve files for download. The file-serving helpers write to the response
directly and return nothing, so return nil yourself:
o.Get("/download", func(c *okapi.Context) error {
c.ServeFile("path/to/file.pdf")
return nil
})
ServeFile, ServeFileAttachment and ServeFileInline use the path as given.
http.ServeFile's ".." precaution only inspects r.URL.Path, so it does not
protect a path your handler assembled. For a name that comes from the request,
use c.ServeFileFrom(root, name), which refuses .. and stays inside root:
o.Get("/files/{name}", func(c *okapi.Context) error {
c.ServeFileFrom("public", c.Param("name"))
return nil
})
Convenience Methods
Okapi provides shorthand methods for common HTTP status codes.
Success Responses
// 200 OK
return c.OK(data)
// 201 Created
return c.Created(data)
// 204 No Content
return c.NoContent()
Client Error Responses
These write the value you pass as the response body, unchanged:
// 400 Bad Request
return c.ErrorBadRequest(payload)
// 401 Unauthorized
return c.ErrorUnauthorized(payload)
// 403 Forbidden
return c.ErrorForbidden(payload)
// 404 Not Found
return c.ErrorNotFound(payload)
Server Error Responses
// 500 Internal Server Error
return c.ErrorInternalServerError(payload)
Returning an Error From a Handler
An error returned by a handler is not sent to the client. It is logged, and
the configured ErrorHandler answers with a generic 500. When you want a
specific status or message, respond with one of the Abort* helpers instead of
returning the error:
o.Get("/books/:id", func(c *okapi.Context) error {
book, err := findBook(c.Param("id"))
if err != nil {
// Logged, and the client gets a generic 500
return err
}
if book == nil {
// Explicit status and message
return c.AbortNotFound("Book not found")
}
return c.OK(book)
})
Advanced Response Handling
Response Struct Binding
When using c.Respond() / c.Return(), Okapi automatically serializes the response struct into the HTTP response.
It inspects the struct to determine:
- the HTTP status code
- response headers
- cookies
- and the response body (encoded according to the
Acceptheader).
type BookResponse struct {
// HTTP status code — the field must be named Status (default: 200)
Status int `json:"status"`
// Response body — the field must be named Body
Body struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
}
// Custom headers
XRequestID string `header:"X-Request-ID" json:"x_request_id"`
// Cookies
SessionID string `cookie:"session_id" json:"session_id"`
}
o.Get("/books/:id", func(c *okapi.Context) error {
response := BookResponse{
Status: http.StatusOK,
Body: struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
}{
ID: 1,
Name: "The Great Go Book",
Price: 20,
},
XRequestID: "req-12345",
SessionID: "sess-67890",
}
return c.Respond(response)
})
How the struct is read:
- A field named
Status, of typeintand greater than zero, sets the HTTP status code. There is nostatusstruct tag — the field name is what matters. Anything else falls back to200. - A field named
Bodyprovides the response body. Again the field name matters, not itsjsontag. header:"Header-Name"sets a response header.cookie:"cookie_name"sets a cookie (path/).- Any other exported field, apart from
StatusandBody, is written as a response header named after itsjsontag, or after the field name when there is none. Keep this in mind before adding an extra field to a response struct. - Unexported fields are skipped.
The body is encoded from the Accept header: XML, YAML, plain text and HTML are
honoured, and anything else — including a missing Accept — is encoded as JSON.
Setting Headers Manually
o.Get("/", func(c *okapi.Context) error {
c.SetHeader("X-Custom-Header", "value")
c.SetHeader("Cache-Control", "max-age=3600")
return c.OK(okapi.M{"message": "Success"})
})
Setting Cookies
c.SetCookie takes the cookie's parts as arguments:
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
o.Get("/login", func(c *okapi.Context) error {
c.SetCookie("session", "abc123", 3600, "/", "", true, true)
return c.OK(okapi.M{"message": "Logged in"})
})
For full control — SameSite, Expires, partitioned cookies — write the cookie
yourself on the response:
o.Get("/login", func(c *okapi.Context) error {
http.SetCookie(c.Response(), &http.Cookie{
Name: "session",
Value: "abc123",
Path: "/",
MaxAge: 3600,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
return c.OK(okapi.M{"message": "Logged in"})
})
Abort Methods
Abort methods immediately stop request processing and send an error response through the configured ErrorHandler. They're useful in middleware or when you need to halt execution:
o.Use(func(c *okapi.Context) error {
token := c.Header("Authorization")
if token == "" {
return c.AbortUnauthorized("Missing authorization token")
}
return c.Next()
})
c.Header(key) reads a request header. Use c.SetHeader(key, value) to set
one on the response.
Available abort methods:
The error argument is optional and variadic; when omitted, the message is used as the underlying error.
// 400 Bad Request
return c.AbortBadRequest("Invalid input", err)
// 401 Unauthorized
return c.AbortUnauthorized("Not authenticated", err)
// 403 Forbidden
return c.AbortForbidden("Access denied", err)
// 404 Not Found
return c.AbortNotFound("Resource not found", err)
// 500 Internal Server Error
return c.AbortInternalServerError("Server error", err)
Template Rendering
Render HTML templates with data:
o.Get("/", func(c *okapi.Context) error {
return c.Render(http.StatusOK, "welcome", okapi.M{
"title": "Welcome Page",
"message": "Hello from Okapi!",
})
})
c.Render requires a renderer to be attached; without one it returns
okapi.ErrNoRenderer. See the Templating section
for details on configuring template engines and on how templates are named.
Examples
Complete CRUD Endpoint
type Book struct {
ID int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
}
// List books
o.Get("/books", func(c *okapi.Context) error {
books := []Book{{ID: 1, Name: "Go Programming"}}
return c.OK(books)
})
// Get single book
o.Get("/books/:id", func(c *okapi.Context) error {
book, ok := findBook(c.Param("id"))
if !ok {
return c.AbortNotFound("Book not found")
}
return c.OK(book)
})
// Create book
o.Post("/books", func(c *okapi.Context) error {
var book Book
if err := c.BindJSON(&book); err != nil {
return c.AbortBadRequest("Invalid request body", err)
}
// Save book...
return c.Created(book)
})
// Delete book
o.Delete("/books/:id", func(c *okapi.Context) error {
if !deleteBook(c.Param("id")) {
return c.AbortNotFound("Book not found")
}
return c.NoContent()
})