---
layout: 'page'
uri: '/framework/presentation/http-handlers'
position: 2
slug: 'framework-presentation-http-handlers'
parent: 'framework-presentation'
navTitle: 'Handlers & Middleware'
title: 'Handlers & Middleware'
description: 'HTTP handlery s bus dispatchem a middleware chain (trace, security headers, CORS, CSRF, logging, JWT).'
---
# Handlers & Middleware
## Proč
Handlery jsou tenká vrstva mezi HTTP a doménou -- deserializují vstup, zavolají command/query přes bus a vrátí odpověď. Middleware chain řeší průřezy (trace, auth, CORS) mimo handlery, takže každý handler zůstává jednoduchý.
## Jak
### Handler pattern
Handler přijme request, dekóduje JSON body a dispatchne přes bus. Neimportuje `infrastructure/` -- autorizace a další průřezy probíhá v bus middleware.
**Konvence pojmenování:** struct odpovídá zdroji/oblasti (`AdminUsersHandler`, `AuthHandler`, `HealthHandler`), metoda odpovídá akci (`Create`, `List`, `Login`, `Check`). Žádný `Handle*` prefix -- struct už říká, že jde o handler, metoda nese význam (akci).
```go
// presentation/http/handler/admin_users.go
type AdminUsersHandler struct {
commandBus *bus.CommandBus
queryBus *bus.QueryBus
createUser *command.CreateUserHandler
listUsers *query.ListUsersHandler
}
func (h *AdminUsersHandler) Create(w http.ResponseWriter, r *http.Request) {
var cmd command.CreateUserCommand
if err := request.DecodeJSON(w, r, &cmd); err != nil {
response.Error(w, http.StatusBadRequest, err)
return
}
err := bus.ExecVoid(r.Context(), h.commandBus.Bus, "CreateUser", cmd, func(ctx context.Context) error {
return h.createUser.Handle(ctx, cmd)
})
if err != nil {
response.HandleError(w, err)
return
}
response.JSON(w, http.StatusCreated, nil)
}
func (h *AdminUsersHandler) List(w http.ResponseWriter, r *http.Request) {
q := query.ListUsersQuery{}
users, err := bus.Exec[[]user.User](r.Context(), h.queryBus.Bus, "ListUsers", q, func(ctx context.Context) ([]user.User, error) {
return h.listUsers.Handle(ctx, q)
})
if err != nil {
response.HandleError(w, err)
return
}
response.JSON(w, http.StatusOK, users)
}
```
Registrace rout čitelně odráží akci:
```go
mux.HandleFunc("POST /api/v1/admin/users", adminUsers.Create)
mux.HandleFunc("GET /api/v1/admin/users", adminUsers.List)
mux.HandleFunc("POST /api/v1/auth/login", auth.Login)
mux.HandleFunc("GET /health", health.Check)
```
- **Command (bez výsledku):** `bus.ExecVoid()` -- použít pro create, update, delete.
- **Query (s výsledkem):** `bus.Exec[R]()` -- typovaný generický návrat.
Chyby z bus dispatche jsou centralizované přes `response.HandleError(w, err)` -- ten mapuje doménové typy na HTTP status (handler se o mapování nestará). Výjimkou je dekódování vstupu: selhání `request.DecodeJSON` handler mapuje explicitně přes `response.Error(w, http.StatusBadRequest, err)`, protože "špatný JSON" je vždy 400 a nemá procházet doménovým mapováním. Viz [Error typy](/framework/domain/errors-events).
### Middleware chain
Balíček `presentation/http/middleware/`. Každý middleware je `func(http.Handler) http.Handler`.
| Middleware | Soubor | Popis |
|---|---|---|
| Trace | `trace.go` | Generování/propagace X-Trace-Id |
| IP | `ip.go` | Resoluce klientské IP do contextu (sdíleno s rate limitem a auditem) |
| Security headers | `security.go` | HSTS (gateováno na `APP_COOKIE_SECURE`), CSP, X-Frame-Options, Permissions-Policy a další |
| CORS | `cors.go` | Povolení cross-origin (Vite dev) |
| CSRF | -- | `http.CrossOriginProtection` (Go 1.25 stdlib) |
| Logging | `logging.go` | Request/response logging |
| JWT Auth | `auth.go` | Validace Bearer tokenu, claims do contextu |
Pořadí chain podle typu routy:
```
Request
/health, /api/v1/auth/{login,refresh}
-> Trace -> IP -> Recovery -> Security headers -> CORS -> CSRF -> Logging -> Handler
/api/v1/... (chráněné)
-> Trace -> IP -> Recovery -> Security headers -> CORS -> CSRF -> Logging -> JWT Auth -> Handler
/{path...} (SPA)
-> Trace -> IP -> Recovery -> Security headers -> CORS -> CSRF -> Logging -> SPA Fallback
```
SPA catch-all (`GET /{path...}`) je registrovaný do **téhož** muxu, který obaluje globální chain -- **neobchází** middleware (žádný static-file bypass). Jen běží jako poslední route, takže explicitní cesty vyhrávají.
Oddělení admin / uživatel routes řeší bus `AuthorizeMiddleware` skrze `Permissioned.RequiredPermission()` -- žádný role-guard HTTP middleware není potřeba, protože pravidlo "admin má všechno, ostatní role jsou zamítnuti pro `admin:*`" platí pro každý command i query.
### Trace middleware
Generuje unikátní trace ID pro každý request a ukládá ho do contextu:
```go
ctx = shared.ContextWithTraceID(r.Context(), traceID)
```
Trace ID je dostupný ve všech dalších vrstvách přes `shared.TraceIDFromContext(ctx)`.
### JWT Auth middleware
Extrahuje Bearer token z `Authorization` hlavičky, validuje přes `security.JwtService` a uloží claims do contextu:
```go
claims, err := jwtService.ValidateAccessToken(token)
ctx = shared.ContextWithClaims(r.Context(), claims)
```
Při nevalidním tokenu vrací `401 Unauthorized`.
## Detaily
- Handlery nikdy neimportují infrastructure balíčky. Všechna business logika (validace, autorizace, persistence) se děje v bus middleware a application layer.
- CSRF ochrana používá `http.CrossOriginProtection` ze stdlib Go 1.25 -- není třeba externí knihovna.
- Context propagace: trace ID i auth claims putuje celým request lifecycle od middleware až po repository vrstvu.
---
[← HTTP Server](/framework/presentation/http-server.md) | [Console →](/framework/presentation/console.md)