Render
The render
package helps manage HTTP request / response payloads. The motivation and
ideas for making this package come from go-chi/render.
Every well-designed, robust and maintainable Web Service / REST API also needs
well-defined request and response payloads. Together with the endpoint handlers,
the request and response payloads make up the contract between your server and the
clients calling on it.
Typically in a REST API application, you will have your data models (objects/structs)
that hold lower-level runtime application state, and at times you need to assemble,
decorate, hide or transform the representation before responding to a client. That
server output (response payload) structure, is also likely the input structure to
another handler on the server.
This is where render
comes in - offering a few simple helpers to provide a simple
pattern for managing payload encoding and decoding.
Render is also combined with some helpers for responding to content types and parsing
request bodies. Please have a look at the examples. examples.
All feedback is welcome, thank you!
Features
- Very simple API
- Render based on Accept header or format query parameter
- Custom render functions JSON, XML, PlainText ...
- Header or body pagination response
- Map of defaults error and statusCode
- Customizable error handling
- Easy decoding request body based on content type
- Switch encoders/decoders with some popular open source lib
Installation
Install render with go get:
go get github.com/enverbisevac/render
Usage/Examples
package main
import (
"fmt"
"net/http"
"strings"
"github.com/enverbisevac/render"
)
type Person struct {
Name string
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
paths := strings.Split(r.URL.Path, "/")
name := paths[len(paths)-1]
render.Render(w, r, Person{
Name: name,
})
}
func createUser(w http.ResponseWriter, r *http.Request) {
user := User{}
if err := render.Decode(r, &user); err != nil {
render.Error(w, r, err)
return
}
render.Render(w, r, user)
}
func dumbLoader(limit, offset int, total *int) []User {
*total = 100
return []User{
{
"Enver",
},
{
"Joe",
},
}
}
func listUsers(w http.ResponseWriter, r *http.Request) {
pagination := render.PaginationFromRequest(r)
data := dumbLoader(pagination.Size(), pagination.Page(), &pagination.Total)
pagination.Render(w, r, data)
}
func errorHandler(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, render.ErrNotFound)
}
func main() {
http.HandleFunc("/hello/", helloHandler)
http.HandleFunc("/create", createUser)
http.HandleFunc("/error/", errorHandler)
http.ListenAndServe(":8088", nil)
}
API Reference
Bind request body to data type v
func Bind(r *http.Request, v interface{}) error
Parameter | Type | Description |
---|
r | *http.Request | Required. Handler request param. |
v | interface{} | Required. Pointer to variable. |
error will be returned if binding fails
func Render(w http.ResponseWriter, r *http.Request, v interface{}, params ...interface{})
Parameter | Type | Description |
---|
w | http.WriterResponse | Required. Writer. |
r | *http.Request | Required. Handler request param. |
v | interface{} | Required. Pointer to variable. |
params | ...interface{} | Variadic number of params. (int/string/http.Header) |
Render error response and status code based on request r
headers
func Error(w http.ResponseWriter, r *http.Request, err error, params ...interface{})
Parameter | Type | Description |
---|
w | http.WriterResponse | Required. Writer. |
r | *http.Request | Required. Handler request param. |
err | error . | Required. Error value. |
params | ...interface{} | Variadic number of params. (int/string/http.Header) |
Params variadic function parameter
params
can be found in almost any function. Param type can be string, http.Header or integer.
Integer value represent status code. String or http.header are just for response headers.
render.Render(w, v, http.StatusOK, "Content-Type", "application/json")
render.Render(w, v, http.StatusOK, render.ContentTypeHeader, render.ApplicationJSON)
render.Render(w, v, "Content-Type", "application/json")
render.Render(w, v, "Content-Type", "application/json", http.StatusOK)
render.Render(w, v, http.Header{
"Content-Type": []string{"application/json"},
}, http.StatusOK)
Integrate 3rd party JSON/XML lib
in this example we will replace standard encoder with goccy/go-json.
package main
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/goccy/go-json"
"github.com/enverbisevac/render"
)
func init() {
render.JSONEncoder = func(w io.Writer) render.Encoder {
return json.NewEncoder(w)
}
}
pagination API function PaginationFromRequest
accepts single parameter of type *http.Request
and returns Pagination
object.
pagination := render.PaginationFromRequest(r)
pagination struct contains several read only fields:
type Pagination struct {
page int
size int
prev int
next int
last int
Total int
}
only field you can modify is Total field. Query values are mapped to pagination object:
http://localhost:3000/users?page=1&per_page=10
then you can process your input pagination data:
data := loadUsers(pagination.Size(), pagination.Page(), &pagination.Total)
when we have data then we can render output:
pagination.Render(w, r, data)
Render output can be placed in headers or body of the response, default one is header,
this setting can be changed by package variable at init function of your project.
List of package variables can be set:
var (
PageParam = "page"
PerPageParam = "per_page"
PerPageDefault = 25
Linkf = `<%s>; rel="%s"`
PaginationInHeader = true
PaginationBody = DefaultPaginationBody
)
Other API functions
func Blob(w http.ResponseWriter, v []byte, params ...interface{})
func PlainText(w http.ResponseWriter, v string, args ...interface{})
func HTML(w http.ResponseWriter, v string, args ...interface{})
func JSON(w http.ResponseWriter, v interface{}, args ...interface{})
func XML(w http.ResponseWriter, v interface{}, args ...interface{})
func File(w http.ResponseWriter, r *http.Request, fullPath string)
func Attachment(w http.ResponseWriter, r *http.Request, fullPath string)
func Inline(w http.ResponseWriter, r *http.Request, fullPath string)
func NoContent(w http.ResponseWriter)
func Stream(w http.ResponseWriter, r *http.Request, v interface{})
more help on API can be found in Documentation.
Documentation
Documentation
FAQ
Last parameter is variadic parameter in Render()
or Error()
function. Then we can
use
render.Render(w, r, object, http.StatusAccepted)
render.Render(w, r, object, http.StatusAccepted, "Content-Type", "application/json")
render.Render(w, r, object, "Content-Type", "application/json")
Register global error/status codes
render.ErrorMap[ErrConflict] = http.StatusConflict
we can even wrap the error:
render.Error(w, r, fmt.Errorf("file %s %w", "demo.txt", render.ErrConflict))
Inline error rendering with status code
render.Error(w, r, errors.New("some error"), http.StatusBadRequest)
or
render.Error(w, r, &render.HTTPError{
Err: errors.New("some error"),
Status: http.StatusBadRequest,
})
Customize error response
type CustomError struct {
Module string `json:"module"`
Message string `json:"message"`
Version string `json:"version"`
}
func (e CustomError) Error() string {
return e.Message
}
render.TreatError = func(r *http.Request, err error) interface{} {
cerr := &CustomError{}
if errors.As(err, &cerr) {
return cerr
}
return render.DefaultErrorRespond(err)
}
Running Tests
To run tests, run the following command
make test
Acknowledgements
License
MIT
Feedback
If you have any feedback, please reach out enver[@]bisevac.com