# Middleware

> Run code around handlers: for every request, for a group or for one route, with errors as values and net/http middleware through tinka.Std.

This guide covers middleware in Tinka. You will learn how to:

- Write middleware that runs before and after the handler
- Apply it to every request, to a group or to one route
- End a request from middleware
- See and handle the errors handlers return
- Use middleware written for `net/http`

## Overview

**Middleware** is code that runs around handlers: it checks a request before the handler sees it, or adjusts the response after. Authentication, CORS, rate limits and timing headers are middleware, because they apply to many routes and have nothing to do with any one handler.

A middleware is a function that receives the [HTTP context](http-context.md) and `next`, which runs the rest of the chain and returns its error. The handler's response is written only after every middleware has returned, so code after `next` can still set headers and the status.

## Write middleware

This middleware measures how long the rest of the request takes, and reports it in a `Server-Timing` header the browser's developer tools display:

```go title="timing.go"
package main

import (
	"fmt"
	"time"

	"github.com/mucolabs/tinka"
)

func timing(c *tinka.Ctx, next tinka.Next) error {
	start := time.Now()
	err := next(c)
	c.Header().Set("Server-Timing", fmt.Sprintf("app;dur=%d", time.Since(start).Milliseconds()))
	return err
}
```

Code before `next` runs on the way in, and code after it on the way out. Return the error `next` returned, so the App can turn it into a response.

## Apply middleware

`app.Use` adds middleware that runs for every request, before routing, in the order added. Requests that match no route pass through it too, so it suits CORS, security headers and request logging:

```go title="main.go"
	app := tinka.New(tinka.HTTPConfig{})
	app.Use(timing)
	app.Get("/posts", listPosts)
```

A router's `Use`, `Group` and `With` add middleware to some routes only, as the [Routing](routing.md) guide shows. For a request to `/admin/posts`, middleware runs from the outside in: the App's, then the group's, then the route's, then the handler. Upstream, after `next`, the order reverses.

```go title="main.go"
	app.Use(timing)                     // every request
	app.Route("/admin", func(r *tinka.Router) {
		r.Use(requireAdmin)             // every route in /admin
		r.With(audited).Delete("/posts/{id}", deletePost) // this route
	})
```

Middleware that takes options is a function that returns one:

```go title="auth.go"
package main

import (
	"github.com/mucolabs/tinka"
)

func requireRole(role string) tinka.Middleware {
	return func(c *tinka.Ctx, next tinka.Next) error {
		user, err := c.Make[*User]()
		if err != nil {
			return err
		}
		if user.Role != role {
			return tinka.Forbidden("Only %ss can do this.", role)
		}
		return next(c)
	}
}
```

Registered with `r.Use(requireRole("editor"))`, it reads the signed-in user, then refuses the request or lets it through.

## End a request

Return an error without calling `next`, and nothing after the middleware runs: the error becomes the response, as `requireRole` above does with `tinka.Forbidden`. Middleware that ran before it still finish their code after `next`, so the timing header is set even on a refused request.

## Handle errors from handlers

`next` returns the handler's error, or the error of any middleware after yours. Inspect it with `errors.Is` and `errors.As`, wrap it, or replace it. Return it to let the App render it:

```go title="metrics.go"
package main

import (
	"errors"

	"github.com/mucolabs/tinka"
)

func countFailures(c *tinka.Ctx, next tinka.Next) error {
	err := next(c)
	if e, ok := errors.AsType[*tinka.Error](err); ok && e.Status >= 500 {
		serverErrors.Add(1)
	}
	return err
}
```

A panic in a handler is not an error: it unwinds through the middleware, skipping their code after `next`, and the App recovers it and answers `500`.

## Use net/http middleware

`tinka.Std` adapts middleware written for `net/http`, with the `func(http.Handler) http.Handler` shape, such as a CORS or compression package:

```go title="main.go"
	app.Use(tinka.Std(cors.Default().Handler)) // github.com/rs/cors
```

The request and the `ResponseWriter` it passes on reach the handler, so a compression middleware compresses the response. Unlike Tinka middleware, `net/http` middleware expects the response to be written when its `next` returns, so the response is written there: middleware outside it still sees the handler's error, but can no longer change the response.

## Time out slow requests

`tinka.Timeout` cancels the request's context after a duration. A handler that passes its context to slow calls then returns early, and the client gets a `503`:

```go title="main.go"
	app.With(tinka.Timeout(5*time.Second)).Get("/reports/{id}", showReport)
```

See also: [Routing](routing.md), [HTTP context](http-context.md), [Exception handling](exception-handling.md)
