Skip to content
You are reading the v0.20 documentation. Go to the latest version (v1.0)

The basics

Responses

Write text, HTML, JSON, files and redirects from handlers, with safe redirect helpers.

Last updated:

Bodies#

Go
c.String(http.StatusOK, "pong")
c.HTML(http.StatusOK, "<h1>Trusted markup only</h1>")
c.JSON(http.StatusOK, web.Map{"ok": true})
c.Blob(http.StatusOK, "image/png", png)
c.Stream(http.StatusOK, "text/csv", reader)
c.NoContent() // 204

Every body helper takes the status code, sets Content-Type and writes the body. Once the status line is sent it cannot change, which is why handlers should return errors instead of writing partial responses.

JSON helpers for APIs#

HelperStatusNotes
c.JSON(status, v)anyencoding/json
c.Created(location, v)201Sets Location when non-empty
c.Accepted(v)202Async work
c.NoContent()204
c.Problem(status, title, detail)4xx/5xxapplication/problem+json (RFC 9457)
Go
func (p *ProductController) Store(c *web.Ctx) error {
	product := p.create(c)
	return c.Created(c.MustURL("products.show", "product", product.ID), product)
}

Redirects#

Go
return c.Redirect("/posts")                     // 302 for GET/HEAD, 303 after POST/PUT/…
return c.RedirectStatus(http.StatusMovedPermanently, "/new")
return c.RedirectRoute("posts.show", "post", 42)
return c.Back("/")                              // same-origin Referer, else fallback
  • After unsafe methods Redirect uses 303 See Other, so the browser follows with GET.
  • htmx requests receive HX-Location (navigation without a full reload) instead of a 3xx.
  • Back ignores off-site referers (open-redirect protection). web.SafeRedirect(r, target, fallback) applies the same check to any user-supplied target, such as a ?next= parameter.

Files#

Go
assets := os.DirFS("storage/reports")

return c.File(assets, "2026/q3.pdf")                       // Range and If-Modified-Since
return c.Download(assets, "2026/q3.pdf", "Q3 report.pdf")  // Content-Disposition: attachment

Missing files and directories return 404. Open user-controlled paths through an os.Root (root.FS()) so .. cannot escape the directory.

Headers and cookies#

Go
c.SetHeader("Cache-Control", "no-store")
c.SetCookie(&http.Cookie{Name: "theme", Value: "dark", Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode})

Conditional requests#

Go
func (pc *PostController) Show(c *web.Ctx) error {
	post, err := pc.find(c)
	if err != nil {
		return err
	}

	etag, err := web.ETagOf(post) // strong ETag from the JSON encoding
	if err != nil {
		return err
	}

	if c.NotModified(etag, post.UpdatedAt) { // sets ETag/Last-Modified, writes 304 when fresh
		return nil
	}

	return c.JSON(http.StatusOK, post)
}

For optimistic concurrency on writes, c.CheckIfMatch(currentETag) returns errs.ErrPreconditionFailed (412) when the client's If-Match does not match.

Views and checked JSON#

  • c.Render, c.Fragment and c.View render HTML: see Views.
  • web.JSONWith(c, status, schema, v) checks outgoing JSON against a z schema: see Validation.