# Routing

> Map URLs and HTTP methods to handlers, with path parameters, groups, prefixes and middleware.

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.Handler` next 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.

```go title="main.go"
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](exception-handling.md) explains.

```go title="posts.go"
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.

```go title="files.go"
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.

```go title="main.go" ins={4-8}
	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:

```go title="main.go" ins={2}
	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:

```go title="main.go"
	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:

```go title="auth.go"
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`.

:::warning
`Use` must come before the routes it applies to. Calling it after a route on the same router, or on one made from it, panics at startup, because those routes would silently skip the middleware.

```go title="main.go" del={2} ins={3}
	app.Get("/posts", listPosts)
	app.Use(logRequests)
	app.With(logRequests).Get("/posts", listPosts)
```

Move `Use` above the routes, or use `With` for a single route.
:::

## 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.

```go title="main.go"
	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](body-parser.md), [App](../../reference/app.md)
