Routing
This guide covers routing in Tinka. You will learn how to:
- Register routes for HTTP methods and serve the App
- Read path parameters
- Group routes under a prefix
- Add middleware to every route, to a group or to one route
- Serve any
http.Handlernext to your routes - Catch route mistakes when the App starts
Overview
Routes connect a method and a URL to a handler. Tinka registers them on the standard library's http.ServeMux, adding groups, prefixes and per-route middleware, so every pattern you know from net/http works unchanged and a request costs no more than it would with ServeMux alone.
Register a route
Create an App with tinka.New, register handlers on it, and serve it. An App is an http.Handler, so any net/http server runs it.
package main
import (
"log"
"net/http"
"time"
"github.com/mucolabs/tinka"
)
func main() {
app := tinka.New(tinka.HTTPConfig{})
app.Get("/posts", listPosts)
app.Get("/posts/{id}", showPost)
srv := &http.Server{
Addr: ":3000",
Handler: app,
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}A handler takes the response writer and the request, and returns an error. Returning the error is enough: the App turns it into the response, as Return errors from handlers explains.
package main
import (
"net/http"
"github.com/mucolabs/tinka"
)
type post struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func listPosts(w http.ResponseWriter, r *http.Request) error {
return tinka.JSON(w, http.StatusOK, []post{{ID: 1, Title: "Hello, Tinka"}})
}
func showPost(w http.ResponseWriter, r *http.Request) error {
id, err := tinka.PathInt(r, "id")
if err != nil {
return err
}
return tinka.JSON(w, http.StatusOK, post{ID: id, Title: "Hello, Tinka"})
}Get, Post, Put, Patch and Delete register one method each. A Get route also answers HEAD, and a request with a method the path doesn't support gets a 405 Method Not Allowed listing the allowed methods in its Allow header.
Read path parameters
A {name} segment matches one path segment, and {name...} matches the rest of the path. Read them with r.PathValue, or with tinka.PathInt when the value must be a whole number, which answers 422 for anything else.
package main
import (
"io"
"net/http"
)
func showFile(w http.ResponseWriter, r *http.Request) error {
_, err := io.WriteString(w, "file: "+r.PathValue("path"))
return err
}Registered as app.Get("/files/{path...}", showFile), a request for /files/docs/intro.md reads docs/intro.md.
Group routes under a prefix
Route gives a set of routes a shared prefix, which may contain parameters. Inside it, "/" is the prefix itself, exactly.
app := tinka.New(tinka.HTTPConfig{})
app.Get("/posts", listPosts)
app.Get("/posts/{id}", showPost)
app.Route("/teams/{team}", func(r *tinka.Router) {
r.Get("/", showTeam)
r.Get("/members", listMembers)
r.Delete("/members/{id}", removeMember)
})This registers GET /teams/{team}, GET /teams/{team}/members and DELETE /teams/{team}/members/{id}. A GET for /teams/go/ answers 308 to /teams/go, so every resource has one canonical URL and links with a trailing slash still work.
Add middleware
Middleware has the standard func(http.Handler) http.Handler shape, so existing net/http middleware works as is. Use applies middleware to the routes registered after it, inside the router you call it on:
app.Route("/admin", func(r *tinka.Router) {
r.Use(requireToken(adminToken))
r.Delete("/posts/{id}", deletePost)
})Group scopes middleware without adding a prefix, and With adds it to one route:
app.Group(func(r *tinka.Router) {
r.Use(requireToken(adminToken))
r.Post("/posts", createPost)
})
slow := app.With(tinka.Timeout(5 * time.Second))
slow.Get("/reports/{id}", showReport)Middleware that can refuse a request returns a tinka.HandlerFunc, so it reports failures the same way handlers do:
package main
import (
"net/http"
"github.com/mucolabs/tinka"
)
func requireToken(token string) tinka.Middleware {
return func(next http.Handler) http.Handler {
check := func(w http.ResponseWriter, r *http.Request) error {
if r.Header.Get("Authorization") != "Bearer "+token {
return tinka.Unauthorized("A valid token is required.")
}
next.ServeHTTP(w, r)
return nil
}
return tinka.HandlerFunc(check)
}
}tinka.Timeout cancels the request's context after the given time. A handler that passes its context to slow calls then returns early, and the client gets a 503.
Serve other handlers
Handle registers any http.Handler, such as a file server, for one method. An empty method matches every method, and a pattern ending in / matches every path below it.
files := http.FileServerFS(assets)
app.Handle("GET", "/assets/", http.StripPrefix("/assets/", files))Catch mistakes at startup
A duplicate route, two patterns that conflict (such as /posts/{id} and /posts/{slug}), or a pattern that doesn't start with / makes registration panic. The App fails when it starts, never on the first request that would have hit the mistake.
See also: Body parser, App