response

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package response describes a successful HTTP answer as a value, before anything is written.

A handler returns a Response: the status, the headers and a JSON body. The value knows nothing about the router, so the same answer can be written by net/http with Write or by a router with its own serializer.

Constructors

JSON answers any value with a status. Data wraps the value in a data envelope; OK and Created are its 200 and 201 forms, and Created sets the Location header:

response.Created("/articles/42", article)
// 201, Location: /articles/42
// {"data":{"id":42,"title":"HTTP boundaries"}}

NoContent answers 204 without a body. Page answers one page of items with the total and the page position:

{"data":{"items":[...],"total":42,"page":2,"size":20}}

Nil items are written as an empty array, so a client never has to tell null from an empty list.

Headers

Location and Content-Type have fields of their own; any other header, such as a custom X-User, goes to Header. Response.SetHeaders writes them all under canonical names, replacing values already set, so x-user and X-User are the same header whichever way a handler spells it.

Writing

Response.Validate rejects a response that must not be written: a status outside 200-599, a body on 204 or 304, a body without a content type, or 201 without a Location.

Write is the net/http backend. It validates the response and encodes the body with encoding/json before it commits anything, so after a failure the writer is untouched and the handler can still answer with an error.

A router that has its own serializer writes a Response the same way: Validate, SetHeaders into the router's response headers, then Body with Status, or only Status when ContentType is empty.

Index

Examples

Constants

View Source
const ContentTypeJSON = "application/json"

ContentTypeJSON is the media type of a JSON response body.

Variables

This section is empty.

Functions

func Write

func Write(w http.ResponseWriter, resp Response) error

Write is the net/http backend. It encodes the body with encoding/json before committing anything, so an invalid response or an encoding failure leaves w untouched.

Example
package main

import (
	"fmt"
	"log"
	"net/http/httptest"

	"github.com/uchaloop/httpx/response"
)

type article struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

func main() {
	recorder := httptest.NewRecorder()

	created := response.Created("/articles/42", article{ID: 42, Title: "HTTP boundaries"})
	if err := response.Write(recorder, created); err != nil {
		log.Fatal(err)
	}

	fmt.Println(recorder.Code)
	fmt.Println(recorder.Header().Get("Location"))
	fmt.Println(recorder.Header().Get("Content-Type"))
	fmt.Println(recorder.Body.String())
}
Output:
201
/articles/42
application/json
{"data":{"id":42,"title":"HTTP boundaries"}}

Types

type Response

type Response struct {
	Status int
	// Location is written as the Location header. Status 201 requires it.
	Location string
	// Header holds any other headers, such as a custom X-User; Content-Type and
	// Location have fields of their own. Names are canonicalized when written.
	Header http.Header
	// ContentType is the JSON media type of Body, and is empty for a response
	// without a body.
	ContentType string
	Body        any
}

Response is what a handler answers, before anything is written. A framework adapter writes it with the framework's own serializer; Write is the net/http backend.

func Created

func Created(location string, value any) Response

Created answers a data envelope with status 201 and a Location header, which must not be empty.

func Data

func Data(status int, value any) Response

Data answers value inside a {"data": ...} envelope. A nil value is written explicitly as {"data":null}; it never changes the status.

func JSON

func JSON(status int, value any) Response

JSON answers value with status.

func NoContent

func NoContent() Response

NoContent answers status 204 without a body or content type.

Example
package main

import (
	"fmt"
	"log"
	"net/http/httptest"

	"github.com/uchaloop/httpx/response"
)

func main() {
	recorder := httptest.NewRecorder()
	if err := response.Write(recorder, response.NoContent()); err != nil {
		log.Fatal(err)
	}

	fmt.Println(recorder.Code, recorder.Body.Len())
}
Output:
204 0

func OK

func OK(value any) Response

OK answers a data envelope with status 200, including for a nil value.

func Page

func Page[Item any](items []Item, total uint64, current page.Page) Response

Page answers one page of items in a data envelope together with the total and the page position. Nil items are written as an empty array.

Example
package main

import (
	"fmt"
	"log"
	"net/http/httptest"

	"github.com/uchaloop/httpx/page"
	"github.com/uchaloop/httpx/response"
)

type article struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

func main() {
	current, err := page.Make(page.Params{}, page.Config{DefaultSize: 2, MaxSize: 100})
	if err != nil {
		log.Fatal(err)
	}

	articles := []article{{ID: 1, Title: "API versions"}, {ID: 2, Title: "HTTP boundaries"}}

	recorder := httptest.NewRecorder()
	if err := response.Write(recorder, response.Page(articles, 5, current)); err != nil {
		log.Fatal(err)
	}

	fmt.Println(recorder.Body.String())

	// A page past the end has no items, written as an empty array.
	recorder = httptest.NewRecorder()
	if err := response.Write(recorder, response.Page[article](nil, 5, current)); err != nil {
		log.Fatal(err)
	}

	fmt.Println(recorder.Body.String())
}
Output:
{"data":{"items":[{"id":1,"title":"API versions"},{"id":2,"title":"HTTP boundaries"}],"total":5,"page":1,"size":2}}
{"data":{"items":[],"total":5,"page":1,"size":2}}

func (Response) SetHeaders

func (r Response) SetHeaders(h http.Header)

SetHeaders writes the response headers into h: Header under canonical names, replacing values already set, then Location and Content-Type. Every backend uses it, so all adapters send the same headers.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/uchaloop/httpx/response"
)

type article struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

func main() {
	answer := response.OK(article{ID: 42, Title: "HTTP boundaries"})
	answer.Header = http.Header{"x-user": {"7"}}

	// A router writes the headers into its own response the same way.
	header := make(http.Header)
	answer.SetHeaders(header)

	fmt.Println(header)
}
Output:
map[Content-Type:[application/json] X-User:[7]]

func (Response) Validate

func (r Response) Validate() error

Validate reports a response that must not be written: a status outside 200-599, a body with a status that forbids one, a body without a content type, or status 201 without a Location.

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/uchaloop/httpx/response"
)

type article struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

func main() {
	answer := response.Response{
		Status:      http.StatusCreated,
		ContentType: response.ContentTypeJSON,
		Body:        article{ID: 42, Title: "HTTP boundaries"},
	}

	fmt.Println(answer.Validate())
}
Output:
status 201 requires a location

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL