Skip to content

Guide

Your First Route

Build one small endpoint end to end, reading params, query values, JSON input, and returning errors.

The Quickstart got a server running. This page builds a slightly more realistic endpoint and introduces the four things nearly every handler does: read the path, read the query, decode a body, and fail cleanly.

Every route handler and every middleware in Zinc has the same signature:

func(c *zinc.Context) error

c holds the request and writes the response. Return nil after writing a response, or return an error and let Zinc turn it into one.

Brace segments in a route pattern become parameters. Query values come from the URL.

app.Get("/teams/{team}/members", func(c *zinc.Context) error {
return c.JSON(zinc.Map{
"team": c.Param("team"),
"role": c.QueryOr("role", "any"),
})
})
Terminal window
curl 'http://localhost:8080/teams/platform/members?role=admin'
# {"role":"admin","team":"platform"}

Declare a struct for the input, then bind into it. Struct tags say where each field comes from.

type CreateMember struct {
Team string `path:"team"`
Name string `json:"name"`
Email string `json:"email"`
}
app.Post("/teams/{team}/members", func(c *zinc.Context) error {
var in CreateMember
if err := c.Bind().All(&in); err != nil {
return zinc.ErrBadRequest.WithMessage("invalid request").WithCause(err)
}
if in.Name == "" {
return zinc.ErrUnprocessableEntity.WithMessage("name is required")
}
return c.Status(zinc.StatusCreated).JSON(in)
})

Bind().All fills Team from the path and Name and Email from the JSON body. When the body is malformed it returns an error, which the handler turns into a 400 Bad Request.

Handlers fail by returning an error. Zinc’s predefined errors carry a status code and an optional client-facing message.

app.Get("/members/{id}", func(c *zinc.Context) error {
member, err := store.Find(c.Param("id"))
if errors.Is(err, ErrNoMember) {
return zinc.ErrNotFound.WithMessage("member not found")
}
if err != nil {
return err // becomes 500 Internal Server Error, details stay private
}
return c.JSON(member)
})
Terminal window
curl -i http://localhost:8080/members/nope
# HTTP/1.1 404 Not Found
# member not found

Any error that is not a Zinc HTTP error becomes a plain 500 Internal Server Error, so internal details never leak to clients. Errors shows how to replace the plain-text body with a JSON envelope.

Route patterns are validated when you register them. A typo such as /users/:id or a conflicting route panics when the program starts, not on the first request, so you never check an error after app.Get.

  • Routing covers every pattern form, groups, and precedence.
  • Binding covers every input source and validation.
  • Errors covers custom messages, metadata, and JSON error responses.