binder / Docs / Install & quick start

Install & quick start

binder has no dependencies and no build step of its own. Adding it is one go get; using it is one function.

Requirements

Go 1.27 or newer. binder's go.mod declares Go 1.27, so earlier toolchains will not build it. See the Go version policy for how that minimum moves.

Install

Fetches the latest release and records it in go.mod.

go get uradical.io/go/binder

Ask for a particular release rather than whatever is newest.

go get uradical.io/go/binder@v1.2.0

Write the import first and let the toolchain work out the rest.

go mod tidy

Either way, import it as binder:

import "uradical.io/go/binder"

Your first handler

A route with a path parameter, an optional query flag and a JSON body — three sources, one struct, one call.

package main

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

	"uradical.io/go/binder"
)

type CreateComment struct {
	PostID  int      `path:"id"`
	Notify  bool     `query:"notify"`
	Author  string   `body:"author,required"`
	Text    string   `body:"text,required"`
	Tags    []string `body:"tags,omitempty"`
}

func createComment(w http.ResponseWriter, r *http.Request) {
	var req CreateComment
	if err := binder.Bind(r, &req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	json.NewEncoder(w).Encode(req)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("POST /posts/{id}/comments", createComment)
	log.Fatal(http.ListenAndServe(":8080", mux))
}

Run it, then send a request that exercises all three sources:

curl -X POST 'http://localhost:8080/posts/42/comments?notify=true' \
  -H 'Content-Type: application/json' \
  -d '{"author":"ada","text":"first","tags":["intro","meta"]}'
response
{"PostID":42,"Notify":true,"Author":"ada","Text":"first","Tags":["intro","meta"]}

Now drop the author key and send it again. Because that field is tagged required, binding fails and the handler answers 400 with a message naming the field and the source it should have come from. Drop text as well, and both are reported, one per line:

response
missing required field Author: no body value named "author"
missing required field Text: no body value named "text"
The error text is deliberately not part of the API — match on ErrMissingRequired rather than on the string, and use BindErrors to answer field by field. See Error handling.

What the body needs to say

Body binding follows the request's Content-Type. Send application/json for JSON, application/x-www-form-urlencoded for a form, or multipart/form-data for a form with files; anything else leaves the body unparsed and the request binds from its path, query, cookie and header values alone (the body is still read, within the size limit, when a field binds from it). The same struct handles both, so an HTML form post and an API call can share a handler:

curl -X POST 'http://localhost:8080/posts/42/comments' \
  -d 'author=ada&text=first&tags=intro&tags=meta'

Where to go next