There are many ways to handle HTTP responses but I'll show you what I like. I personally prefer/use the first version in my projects because all the options are optional. It is useful if you have a certain JSON way of handling responses in your Go applications. You can set headers as well.


Option 1


package response

import (
"encoding/json"
"errors"
"net/http"
)

type response struct {
code int
headers map[string]string
Action string `json:"action,omitempty"`
Data interface{} `json:"data,omitempty"`
Meta interface{} `json:"meta,omitempty"`
Message string `json:"message,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}

// Write sends the response to the client. The `response` fields can be
// overridden by passing variadic `opts` ("Functional Options") arguments. If
// no options are given, an empty `200` response is used.
func Write(w http.ResponseWriter, opts ...Option) error {
if len(opts) == 0 {
w.WriteHeader(http.StatusOK)
return nil
}

r := &response{code: http.StatusOK}

for _, opt := range opts {
opt(r)
}

if r.code == 0 {
return errors.New("0 is not a valid code")
}

for k, v := range r.headers {
w.Header().Add(k, v)
}

if !isBodyAllowed(r.code) {
w.WriteHeader(r.code)
return nil
}

body, err := json.Marshal(r)
if err != nil {
return err
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(r.code)

if _, err := w.Write(body); err != nil {
return err
}

return nil
}

// isBodyAllowed reports whether a given response status code permits a body.
// See RFC 7230, section 3.3.
func isBodyAllowed(status int) bool {
if (status >= 100 && status <= 199) || status == 204 || status == 304 {
return false
}

return true
}

// -----------------------------------------------------------------------------

// Option helps overriding/adding response options to the current response.
type Option func(*response)

// Code sets status code.
func Code(code int) Option {
return func(r *response) {
r.code = code
}
}

// Headers adds headers.
func Headers(headers map[string]string) Option {
return func(r *response) {
for k, v := range headers {
r.headers[k] = v
}
}
}

// Success represents "successful" response.
func Success(action string, data interface{}, meta interface{}) Option {
return func(r *response) {
r.Action = action
r.Data = data
r.Meta = meta
r.Message = ""
r.Errors = nil
}
}

// Error represents "failure" response.
func Error(action string, message string, errors map[string]string) Option {
return func(r *response) {
r.Action = action
r.Message = message
r.Errors = errors
r.Data = nil
r.Meta = nil
}
}

Usage


response.Write(w)

response.Write(w, response.Code(http.StatusNoContent))

response.Write(w,
response.Code(http.StatusBadRequest),
response.Error("users", "Invalid request", map[string]string{"name":"Required field"}),
)

response.Write(w,
response.Code(http.StatusInternalServerError),
response.Error("users", "Internal error occurred", nil),
)

Option 2


package response

import (
"encoding/json"
"errors"
"net/http"
)

type Response struct {
// Shared common fields.
Code int `json:"-"`
Headers map[string]string `json:"-"`
Action string `json:"action,omitempty"`

// Success specific fields.
Data interface{} `json:"data,omitempty"`
Meta interface{} `json:"meta,omitempty"`

// Failure specific fields.
Message string `json:"message,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}

func Write(w http.ResponseWriter, r Response) error {
if r.Code == 0 {
return errors.New("0 is not a valid code")
}

for k, v := range r.Headers {
w.Header().Add(k, v)
}

if !isBodyAllowed(r.Code) {
w.WriteHeader(r.Code)
return nil
}

body, err := json.Marshal(r)
if err != nil {
return err
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(r.Code)

if _, err := w.Write(body); err != nil {
return err
}

return nil
}

// See RFC 7230, section 3.3.
func isBodyAllowed(status int) bool {
if (status >= 100 && status <= 199) || status == 204 || status == 304 {
return false
}

return true
}

Usage


response.Write(w, response.Response{
Code: http.StatusInternalServerError,
Action: "users",
Message: "An internal error has occurred.",
})

response.Write(w, response.Response{
Code: http.StatusCreated,
Action: "users",
Data: something,
})

response.Write(w, response.Response{Code: http.StatusNoContent})

Option 3


package response

import (
"encoding/json"
"net/http"
)

type Success struct {
Action string `json:"action,omitempty"`
Data interface{} `json:"data,omitempty"`
Meta interface{} `json:"meta,omitempty"`
}

type Error struct {
Action string `json:"action,omitempty"`
Message string `json:"message,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}

// NewSuccess returns `*Success` instance.
func NewSuccess() *Success {
return &Success{}
}

// NewError returns `*Error` instance.
func NewError() *Error {
return &Error{}
}

// Write calls `write` function to write success response.
func (s *Success) Write(w http.ResponseWriter, code int, headers map[string]string) {
write(w, s, code, headers)
}

// Write calls `write` function to write error response.
func (e *Error) Write(w http.ResponseWriter, code int, headers map[string]string) {
write(w, e, code, headers)
}

// Write writes final response.
func write(w http.ResponseWriter, d interface{}, c int, h map[string]string) error {
if c == 0 {
return errors.New("0 is not a valid code")
}

for k, v := range h {
w.Header().Set(k, v)
}

if !bodyAllowed(c) {
w.WriteHeader(c)
return nil
}

b, e := json.Marshal(d)
if e != nil {
return e
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(c)

if _, e := w.Write(b); e != nil {
return e
}

return nil
}

// bodyAllowed reports whether a given response status code permits a body. See
// RFC 7230, section 3.3.
func bodyAllowed(status int) bool {
if (status >= 100 && status <= 199) || status == 204 || status == 304 {
return false
}

return true
}

Usage


response.NewSuccess().Write(w, http.StatusNoContent, nil)

res := response.NewError()
res.Action = "users"
res.Message = "Invalid request"
res.Errors = map[string]string{"name":"Required field"}
res.Write(w, http.StatusBadRequest, nil)

res := response.NewError()
res.Action = "users"
res.Write(w, http.StatusInternalServerError, nil)

Example response


500 Internal Server Error
{
"action": "users",
"message": "An internal error has occurred."
}

400 Bad Request
{
"action": "users",
"message": "An invalid request was provided.",
"errors": {
"address": "Invalid value.",
"name": "Invalid value."
}
}

200 OK
{
"action": "users",
"data": {
"uuid": "ac6ae584-7820-4104-ab1d-06bafb07cc65",
"name": "Name",
"address": "Address"
}
}