<!--
  Advanced Topics
  From the Iris book, chapter 24, covering Iris 14.0.0 on Go 1.27.
  Import path: github.com/kataras/iris/v14
  Canonical page: https://iris-go.com/docs/advanced-topics/
-->
# Chapter 24: Advanced Topics

Iris has no plugin system, and it does not need one: its extension model is
plain Go. Every advanced capability in this chapter is an ordinary,
verifiable API: generic controllers that generate REST routes from your
types, typed contexts that replace `iris.Context` in your handler
signatures, global patches that swap framework internals such as the JSON
encoder, custom path parameter types, raw router wrappers, a
multi-application registry, and interop with `net/http`. This final
chapter walks through each extension point and closes with the built-in
controllers that ship with the framework.

## Background

### What an extension point is

An extension point is a place where a framework lets you replace or
extend its own machinery rather than merely use it: swap the JSON
encoder behind `ctx.JSON`, teach the router a new path parameter type,
run code before routing even begins. Reaching for one is justified in
two situations: you keep repeating the same workaround in many
handlers because a framework default does not fit your case, or you are
integrating tooling that predates or lives outside Iris. Be clear about
the flip side: most applications never need this chapter. The first
twenty-two chapters cover everyday application work; if you are reading
the book front to back, it is enough to remember what exists here and
return when a concrete need appears.

### What generics buy the MVC controllers

Go generics (*type parameters*, added to the language in Go 1.18)
let one piece of code be written against placeholder types and
specialized by the compiler for the concrete types you supply. The
first section uses them as a code generator: from five typed method
signatures the framework derives routes, request decoding, response
encoding and error handling. The alternative in older frameworks is
reflection (inspecting types while the program runs), which costs
performance and turns a wrong method shape into a runtime surprise.
With generics the same wiring is checked when you compile: a controller
whose signatures do not match the contract fails the build, not a
production request.

### What process-global means

A *process* is one running instance of your compiled program, the
unit the operating system starts and stops. State that is
process-global is shared by everything inside that binary: every Iris
application in it, every library it imports, every test in the same
`go test` run. Two features below are process-global (`iris.Patches()`
and the application registry behind the `apps` package), and such state
demands caution for the reason globals always do: whoever writes last
wins, and every reader everywhere is affected. The rules that keep it
safe appear in their sections; the short version is mutate once, at
startup, from `main`, never from a library.

## Generic MVC Controllers

The routing controllers of [Chapter 4](https://iris-go.com/docs/routing-controllers/) give you
full control: a `Routing(r iris.Router)` method and any routes you like. The
`controller/mvc` package sits one level higher. You implement five typed
methods on a struct and the framework generates the REST routes, decodes
requests, encodes responses, and wires the error pipeline of
[Chapter 9](https://iris-go.com/docs/error-handling/), all without reflection at serve time.

### The Controller Interface

```go
// github.com/kataras/iris/v14/controller/mvc
type Controller[Context handler.ContextType, Request any, Response any,
    ID iris.IDType, Filter any, Total iris.CountType] interface {
    Create(Context, Request) (Response, error)
    Get(Context, ID) (Response, error)
    Update(Context, ID, Request, ...string) (*Response, bool, error)
    Delete(Context, ID) (bool, error)
    List(ctx Context, filter Filter, limit, offset int) (
        []Response, Total, error)
}
```

The six type parameters, honestly:

| Parameter  | Constraint            | Meaning                                                                        |
| ---------- | --------------------- | ------------------------------------------------------------------------------ |
| `Context`  | `handler.ContextType` | `iris.Context` or a standard `context.Context`; your methods may accept either |
| `Request`  | `any`                 | The input payload, decoded from the request                                    |
| `Response` | `any`                 | The output model; also names the generated path parameter                      |
| `ID`       | `iris.IDType`         | `string \| int \| int64 \| uint64 \| uuid.UUID`                                |
| `Filter`   | `any`                 | The `List` filter payload                                                      |
| `Total`    | `iris.CountType`      | Any integer or float type for the list total                                   |

At the call site you write only the controller type; the compiler infers the
rest from your method signatures. `iris.CountType` is defined in
`core/pkg/pagination` as `constraints.Number`, and it and the other numeric
bounds the framework's generics use come from `core/pkg/constraints`, a
six-interface package that mirrors the subset of
`golang.org/x/exp/constraints` Iris needed, so the module carries no
dependency on the experimental repository.

### A Complete Controller

```go
package main

import (
    "github.com/kataras/iris/v14"
    "github.com/kataras/iris/v14/controller/mvc"
)

type Store struct {
    ID       string `json:"id"`
    Name     string `json:"name"`
    Location string `json:"location"`
}

type StoreFilter struct {
    Location string `json:"location"`
}

type StoreService struct{ /* database handle */ }

func NewStoreService() *StoreService { return &StoreService{} }

type StoreController struct {
    Service *StoreService // injected by the DI container.
}

func (c *StoreController) Create(ctx iris.Context, in Store) (Store, error) {
    in.ID = "generated"
    return in, nil
}

func (c *StoreController) Get(ctx iris.Context, id string) (Store, error) {
    return Store{ID: id, Name: "Bookstore", Location: "New York"}, nil
}

func (c *StoreController) Update(ctx iris.Context, id string, in Store,
    columns ...string) (*Store, bool, error) {
    return &in, true, nil
}

func (c *StoreController) Delete(ctx iris.Context, id string) (bool, error) {
    return true, nil
}

func (c *StoreController) List(ctx iris.Context, filter StoreFilter,
    limit, offset int) ([]Store, int, error) {
    stores := []Store{{ID: "1", Name: "Bookstore", Location: "New York"}}
    return stores, len(stores), nil
}

func main() {
    app := iris.New()
    app.RegisterDependency(NewStoreService)
    app.Router("/stores", mvc.UseController[*StoreController]())
    app.Listen(":8080")
}
```

`UseController` resolves the controller through the dependency injection
container of [Chapter 8](https://iris-go.com/docs/dependency-injection/), or constructs it and
binds its exported fields, then registers these routes:

| Method, path                                 | Controller method | Success response                                           |
| -------------------------------------------- | ----------------- | ---------------------------------------------------------- |
| `POST /stores`                               | `Create`          | 201, JSON body                                             |
| `GET /stores/{store_id}`                     | `Get`             | 200, JSON body                                             |
| `PUT /stores/{store_id}?columns=a&columns=b` | `Update`          | 200 if `*Response` non-nil, else 204 (true) or 304 (false) |
| `DELETE /stores/{store_id}`                  | `Delete`          | 204 (true) or 304 (false)                                  |
| `POST /stores/list`, `QUERY /stores/list`    | `List`            | 200, paginated JSON                                        |

The path parameter name derives from the response model (`Store` becomes
`store_id`); its macro type derives from the `ID` type parameter: `int`,
`int64` and `uint64` map to the matching macro, `uuid.UUID` maps to
`{...:uuid}`, and a `string` ID stays a plain `{...:string}`. Declare the
controller with `uuid.UUID` when you want the uuid macro's validation.
(`iris.IDType` permits only those five, so there is no `uint` case to
reach.) Errors returned by any method
flow through the `rest/errors` pipeline automatically: the controller
installs `errors.Handler` on its child router for you.

\[Diagram: The two type parameters of a controller decide one shared path parameter, and each of the five interface methods becomes a route wrapped in the decode, call and respond pipeline.]

### Resource Options

`UseController` takes a variadic `...mvc.Option`, and the five options
below cover what a generated resource usually needs to say about itself.
Existing `UseController()` calls compile unchanged.

```go
app.Router("/stores", mvc.UseController[*StoreController](
    mvc.Guard(auth.RequireAdmin),
    mvc.Without(mvc.Delete),
    mvc.IDParam("id"),
    mvc.Pagination(iris.AllowSort("name", "created_at")),
))
```

| Option                    | What it does                                                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `mvc.Guard(handlers...)`  | Registers middleware on the write routes only: `Create`, `Update`, `Delete`                                               |
| `mvc.Without(verbs...)`   | Drops generated routes. The verbs are `mvc.Create`, `mvc.Get`, `mvc.Update`, `mvc.Delete` and `mvc.List`                  |
| `mvc.IDParam(name)`       | Overrides the derived parameter name; the macro still comes from the ID type                                              |
| `mvc.BeforeWrite(fn)`     | Runs on `Create` and `Update`, after the body is decoded and before the controller sees it                                |
| `mvc.Pagination(opts...)` | The list route's sort policy: `iris.AllowSort`, `iris.DenySort`, or `errors.<Code>.DenySort` to answer with a chosen code |

`Guard` exists because "everyone may read, only a signed-in caller may
write" is the common shape and a router-level `Use` cannot express it
without splitting the resource into two routers. A dropped route under
`Without` is not registered at all, so it answers 404, or 405 when the
application enables `FireMethodNotAllowed` and another method of the same
path survives.

`BeforeWrite` is how a value the client must not choose gets stamped on
every body in one place, which is the other half of the request scope from
[Chapter 10](https://iris-go.com/docs/authentication-authorization/):

```go
mvc.BeforeWrite(func(ctx iris.Context, v *Store) error {
    tenantID, ok := auth.ScopeID[string](ctx)
    if !ok {
        return errors.Unauthenticated
    }

    v.TenantID = tenantID
    return nil
})
```

The model type is a type parameter, so the hook receives `*T` directly and
no reflection happens at serve time. Three of the options fail loudly
rather than quietly: a `BeforeWrite` declared for a type the resource does
not write panics when the routes are registered, naming both types,
because what it was silently skipping is typically "stamp the tenant id on
this body" and the symptom would otherwise be a cross-tenant write.
`Without` panics on an unknown verb and `IDParam` on an empty name, both
at option-construction time.

### Routes Straight Over a Repository

When a resource's HTTP half adds nothing to its store, there is no
controller type left to write. `mvc.UseRepository` registers the same six
routes directly over the repository:

```go
app.Router("/invoices", mvc.UseRepository(invoiceRepository))
```

The repository is the five methods a service already has, over the
*standard* context: a repository never sees Iris.

```go
type Repository[T any, ID iris.IDType, Filter any] interface {
    Create(ctx context.Context, v T) (ID, error)
    Get(ctx context.Context, id ID) (T, error)
    Update(ctx context.Context, id ID, v T, onlyColumns ...string) (
        updated bool, err error)
    Delete(ctx context.Context, id ID) (deleted bool, err error)
    List(ctx context.Context, filter Filter, limit, offset int) (
        []T, int64, error)
}
```

`POST /` renders 201 `{"id": ...}`, `GET /{model_id}` the model, `PUT` and
`PATCH /{model_id}` a 204 or a 304 through `ctx.NoContent`,
`DELETE /{model_id}` the same, and `POST` or `QUERY /list` the page the `List` route
already renders. The parameter name and its macro derive from the model
and its ID type exactly as they do for `UseController`, so a `uuid.UUID`
id registers `{invoice_id:uuid}` and validates before any handler runs,
and every option from the table above applies here too.

One deliberate difference: the update routes read their body and columns
through `ctx.BindUpdate` ([Chapter 5](https://iris-go.com/docs/reading-requests/)), so a
column that is not a member of the model answers 400 naming it and the
members it could have been. The controller routes cannot do that, because
their request type is decoded through a pluggable decoder. The names that
reach the repository are still the client's, so a repository that builds
SQL from them maps them to its own columns and never interpolates them.

Reach for a controller as soon as a route needs anything the repository
does not do: a mail on create, a computed response, a second store to keep
in sync.

### Custom Routes and the Generated Parameter

A controller may additionally implement `Routing(r iris.Router)` to add
routes beside the generated ones. `mvc.GetPathParam` returns the generated
parameter's name, macro type and route path:

```go
func (c *StoreController) Routing(r iris.Router) {
    param := mvc.GetPathParam(c) // {Name: "store_id", Type: "uuid", ...}
    r.Get("/featured", func(ctx iris.Context) {
        ctx.Writef("featured stores (id param: %s)", param.Name)
    })
}
```

`Routing` runs before the five generated routes are registered, so an
`r.Use` here guards those too:

```go
func (c *StoreController) Routing(r iris.Router) {
    r.Use(auth.RequireAdmin) // protects Create, Get, Update, Delete, List
}
```

The ordering is what makes that work. A route captures the router's
middleware at the moment it is created, so middleware added after a route
exists never reaches it.

### One Controller, Every Request

A controller is a singleton. It is resolved once, its exported fields are
bound once, and its five methods are captured into handlers at
registration. There is no per-request instance, so every field is shared
across all concurrent requests:

```go
type StoreController struct {
    Service *StoreService // fine: written once, read everywhere
    current *Store        // wrong: one request's value, visible to all
}
```

Keep fields for dependencies. Anything that belongs to a single request
belongs on the context or in a method parameter.

### Nested Controllers

`UseController` returns a `*DeployedController`. Its
`With(relPathWithoutParam, controllers...)` method mounts children under the
parent's id segment:

```go
app.Router("/stores", mvc.UseController[*StoreController]().
    With("/books", mvc.UseController[*BookController]()))
// BookController serves under /stores/{store_id}/books
```

A child reads the parent's path parameter through an auto-injected
`mvc.PathParam[T, V]` field, where `T` is the parent response type and `V`
its ID type:

```go
type BookController struct {
    Service    *BookService
    StoreParam *mvc.PathParam[Store, string]
}

func (c *BookController) Create(ctx iris.Context, book Book) (Book, error) {
    book.StoreID = c.StoreParam.Get(ctx) // or GetString(ctx)
    return book, nil
}
```

`DeployedController.WithViews` adds HTML endpoints beside the JSON ones
for controllers that also implement the view methods (`IndexView`,
`CreateView`, `DetailsView`, `UpdateView`, `DeleteView`, `ListView`)
against a registered view engine. It is a generic method: the four type
arguments name the data types those methods return, and a controller
that does not implement `mvc.ViewController` for them panics at
registration time rather than at first request:

```go
app.Router("/books", mvc.UseController[*BookController]().
    WithViews[IndexData, CreateData, ModelData, ListData]())
```

The generated routes are `GET /`, `/create`, `/{id}/details`,
`/{id}/update`, `/{id}/delete` and `/list`, and the template names
derive from the controller's model name plus
`mvc.DefaultTemplateExtension` (`books/index.html` and so on).

### The handler Subpackage

The machinery behind `UseController` is public in `controller/mvc/handler`
and usable route-by-route. `handler.Handler` wraps a typed function into an
`iris.Handler` with the same decode/encode/error pipeline; options tune it:

```go
import "github.com/kataras/iris/v14/controller/mvc/handler"

// POST /orders: decode Order from the body, respond 201.
app.Post("/orders", handler.Handler(createOrder,
    handler.WithStatusCode(iris.StatusCreated)))

// GET /orders/{order_id:int64}: decode the named path parameter
// into the function's second argument.
app.Get("/orders/{order_id:int64}", handler.Handler(getOrder,
    handler.WithPathParameter[int64]("order_id")))

func createOrder(ctx iris.Context, in Order) (Order, error) { /* ... */ }
func getOrder(ctx iris.Context, id int64) (Order, error)    { /* ... */ }
```

For single-value types (`string`, `int`, `int64`, `uint64`, `uuid.UUID`) the
default decoder already reads the last path parameter;
`WithPathParameter[T](name)` makes the binding explicit by name.
`handler.IndexHandler` wraps a `func(Context) (Response, error)` action with
no request payload.

### WebSocket and gRPC Controllers

Both protocols in this section depart from HTTP's request-response
rhythm. A *WebSocket* is a long-lived, two-way connection: after an
initial HTTP handshake, client and server can each push messages at
any moment, the transport behind chats, live dashboards and games.
*gRPC* is a remote procedure call protocol: instead of paths and JSON,
a client calls typed methods defined in a shared `.proto` schema, with
binary messages carried over HTTP/2, a common choice for
backend-to-backend APIs.

Two more controller forms ship in the same package. `mvc.Websocket`
registers WebSocket controllers backed by the third-party
`github.com/kataras/neffos` library; struct pointers or constructor
functions, resolved through DI:

```go
app.Router("/websocket", mvc.Websocket(newChatController))
```

A WebSocket controller may implement `Namespace() string` and the standard
neffos events (`OnNamespaceConnect`, `OnNamespaceConnected`,
`OnNamespaceDisconnect`, `OnRoomJoin`, `OnRoomJoined`, `OnRoomLeave`,
`OnRoomLeft`); any other exported method with the
`func(*neffos.NSConn, neffos.Message) error` shape becomes a custom event.

`mvc.GRPC` serves a gRPC server (third-party `google.golang.org/grpc`)
through the Iris router, POST-only, at `/{ServiceName}/{Method}` paths:

```go
grpcServer := grpc.NewServer()
pb.RegisterGreeterServer(grpcServer, ctrl)

app.Router("/", mvc.GRPC(ctrl).
    Server(grpcServer).
    ServiceName("helloworld.Greeter").
    Strict(false))
```

With `Strict(true)` only gRPC clients are served and plain HTTP requests
receive 404; set it to true for streaming.

### When Generic Controllers Pay Off

Reach for `mvc.UseController` when you have several resources that genuinely
fit the Create/Get/Update/Delete/List shape and you want identical REST
semantics, error handling and pagination across all of them: the five
signatures are a contract, and uniformity is the reward. When a resource is
not CRUD-shaped, or its routes need individual middleware and status
conventions, a plain routing controller from
[Chapter 4](https://iris-go.com/docs/routing-controllers/) is the better tool. The two styles
compose freely: a generic controller's `Routing` method and `With` children
are ordinary routers.

## Custom Typed Contexts

Handlers that keep reading the same values out of `ctx.Values()` and casting
them accumulate noise. `iris.NewContextWrapper` lets you define your own
context type (embedding `iris.Context`) and write handlers against it
directly:

```go
package main

import (
    "github.com/kataras/iris/v14"
)

// appContext extends iris.Context with application-specific helpers.
type appContext struct {
    iris.Context
}

// SetContext makes *appContext usable by the wrapper's default pool.
func (c *appContext) SetContext(ctx iris.Context) {
    c.Context = ctx
}

func (c *appContext) TenantID() string {
    return c.Values().GetString("tenant_id")
}

func main() {
    app := iris.New()

    // The pointer type must implement SetContext(iris.Context).
    w := iris.NewContextWrapper[appContext]()

    app.Get("/dashboard", w.Handler(func(ctx *appContext) {
        ctx.JSON(iris.Map{"tenant": ctx.TenantID()})
    }))

    app.Listen(":8080")
}
```

`w.Handler` converts a `func(*appContext)` into a standard `iris.Handler`:
per request it acquires an `*appContext`, calls `SetContext` with the live
Iris context, runs your function, and releases the value. Because the custom
type embeds `iris.Context`, every context method remains available, and you
can override methods or add helpers as needed.

The wrapper offers more converters for the places Iris expects specific
function shapes: `Handlers` (a slice at once), `HandlerReturnError`,
`HandlerReturnDuration` and `Filter`.

A `sync.Pool`, from Go's standard library, is a recycling bin for
short-lived objects: instead of allocating a fresh value for every
request and leaving it to the garbage collector, code takes an instance
from the pool and puts it back when done, reducing allocation pressure
under load.

The default pool allocates a zero value per request. To recycle instances,
build the wrapper from the `context` subpackage with a `sync.Pool`-backed
pool:

```go
import "github.com/kataras/iris/v14/context"

w := context.NewContextWrapperWithPool(
    context.NewContextPool[appContext]())
```

With a recycling pool, `SetContext` is your reset hook: it runs on every
acquire, so reset any custom fields there: instances are reused across
requests.

## Global Patches

`iris.Patches()` returns a process-wide singleton for overriding framework
defaults. These are the patch points:

| Patch                                                       | Effect                                                                                                       |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `Patches().Context().Writers().JSON(fn)`                    | Replaces the writer behind `ctx.JSON`                                                                        |
| `Patches().Context().Writers().JSONP/XML/Markdown/YAML(fn)` | Same for the other body writers                                                                              |
| `Patches().Context().GetDomain(fn)`                         | Changes how `Context.Domain()` derives the domain from host:port                                             |
| `Patches().Context().SetDefaultCookieExpiration(d)`         | Changes the lifetime `Context.AddCookie` gives a cookie with no `CookieExpires` option (365 days by default) |
| `Patches().Context().ResolveHTTPFS(fn)`                     | Changes filesystem resolution for the file server                                                            |
| `Patches().Context().ResolveFS(fn)`                         | Changes filesystem resolution for the view engine                                                            |
| `Patches().Context().SetMaxPageSize(n)`                     | Sets `pagination.MaxPageSize`, the upper bound of `PageOptions.GetLimit`                                     |
| `Patches().Context().SetDefaultPageSize(n)`                 | Sets `pagination.DefaultPageSize`, used when a list request sends no size                                    |

The classic use of the JSON patch point was swapping in a faster
third-party encoder (jsoniter, easyjson). That trade no longer exists:
since Go 1.27 `ctx.JSON` streams through the standard library's
`encoding/json/v2`, redesigned around the same streaming approach those
libraries were built for, so the stdlib encoder is already the fast
path. What the seam is still for is policy: a binary that must
pretty-print every response, or enforce one marshaling option set
everywhere, patches once instead of threading options through every
call:

```go
package main

import (
    "encoding/json/jsontext"
    json "encoding/json/v2"

    "github.com/kataras/iris/v14"
)

func main() {
    // Process-global: applies to every ctx.JSON call
    // of every Iris application in this binary.
    iris.Patches().Context().Writers().JSON(
        func(ctx iris.Context, v any, options *iris.JSON) error {
            return json.MarshalWrite(ctx.ResponseWriter(), v,
                jsontext.WithIndent("  ")) // house style: always indented.
        })

    app := iris.New()
    app.Get("/", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"encoder": "stdlib, patched"})
    })
    app.Listen(":8080")
}
```

(For a per-call version of the same control, pass options in the
`iris.JSON` struct's `MarshalOptions` field instead; see
[Chapter 6](https://iris-go.com/docs/response-handling/). A type that implements
`encoding/json/v2`'s `json.MarshalerTo` interface streams itself
through plain `ctx.JSON` with no patch at all, which replaces the old
`ctx.EasyJSON` special case.)

Two caveats keep patches honest. First, they are process-global: a patch
affects every application in the binary, including test servers, so
libraries must never patch; only the `main` package should. Second, apply
them once, at startup, before serving; patches are package-level variables
underneath, not synchronized for concurrent replacement.

## Custom Path Macros

[Chapter 4](https://iris-go.com/docs/routing-controllers/) covers the built-in path parameter
types (`{id:int}`, `{id:uuid}`, `{p:path}` and friends). `app.Macros()`
exposes the registry behind them, so you can extend an existing type with a
new function or register an entirely new parameter type.

Add a function to an existing type with `Get` + `RegisterFunc`. The outer
function receives the compile-time arguments from the route pattern; the
returned function validates each request's value:

```go
app.Macros().Get("string").RegisterFunc("equals",
    func(expected string) func(string) bool {
        return func(paramValue string) bool {
            return paramValue == expected
        }
    })

// Usage:
app.Get("/status/{state:string equals(active)}", handler)
```

Register a new parameter type with `Register`. The evaluator converts the
raw segment into a typed value, or reports failure:

```go
import "time"

app.Macros().Register("datetime", "", time.Time{}, false, false,
    func(paramValue string) (any, bool) {
        t, err := time.Parse("2006-01-02", paramValue)
        if err != nil {
            return nil, false
        }
        return t, true
    })

app.Get("/events/{when:datetime}", func(ctx iris.Context) {
    when := ctx.Params().GetTime("when") // the stored time.Time, as is
    ctx.JSON(iris.Map{"weekday": when.Weekday().String()})
})
```

The `Register` arguments after the alias are the sample value (its type
becomes the parameter's Go type), whether the type is the master default,
and whether it is trailing (consumes the rest of the path, like `path`).

When a typed parameter fails to evaluate, builder-built applications respond
with 400 `INVALID_ARGUMENT` ("invalid path parameter") by default.
`SetErrorHandler` (on the whole registry or a single macro) replaces that
behavior:

```go
import "github.com/kataras/iris/v14/rest/errors"

app.Macros().SetErrorHandler(
    func(ctx iris.Context, paramIndex int, err error) {
        errors.InvalidArgument.Detailf(ctx, "bad path parameter",
            "parameter %d: %v", paramIndex, err)
    })
```

## Wrapping the Router

`net/http` is the HTTP server in Go's standard library, the foundation
every Go web framework, Iris included, builds on. Its two base types
are `http.ResponseWriter`, which writes the response, and
`*http.Request`, which describes the request; a router wrapper works
directly at that level, before any `iris.Context` exists.

Middleware runs after Iris has acquired a context and, for `Use`, matched a
route. Router wrappers run earlier: raw `net/http` code executed before
routing, for every request. The wrapper signature is:

```go
func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
```

Call `next` to continue into the router (or the next wrapper); skip it to
short-circuit. Because the wrapper sees the request before any matching
happens, it is the right place for cross-cutting URL rewrites:

```go
package main

import (
    "net/http"
    "strings"

    "github.com/kataras/iris/v14"
)

func main() {
    app := iris.New()

    // Serve the old /legacy/* URLs from the /v2 routes,
    // without redirects and without touching the routes themselves.
    app.WrapRouter(func(w http.ResponseWriter, r *http.Request,
        next http.HandlerFunc) {
        if p, ok := strings.CutPrefix(r.URL.Path, "/legacy/"); ok {
            r.URL.Path = "/v2/" + p
        }
        next(w, r)
    })

    app.Get("/v2/users", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"version": 2})
    })

    app.Listen(":8080")
}
```

Three registration methods exist on the root router, differing only in
ordering:

- `WrapRouter(fn)`: wraps immediately; with multiple calls the last
  registered wrapper runs first (each new wrapper wraps the previous chain).
- `AddRouterWrapper(fn)`: defers wrapping until the router builds; the
  first registered runs first. Use it for redirect-style wrappers that must
  observe the request before others rewrite it.
- `PrependRouterWrapper(fn)`: like `AddRouterWrapper` but always first;
  reserved for path-normalization concerns.

Choose `UseRouter` (Iris middleware, has a `Context`, still before route
matching) when you need context features; choose a router wrapper when you
need to mutate the raw request or integrate `net/http`-level tooling.

At the extreme end, `app.Downgrade(handlerFunc)` replaces the entire Iris
router with your own `http.HandlerFunc`. The application still builds, hosts
and shuts down normally, but routing (and every wrapper) is bypassed; you
are responsible for everything.

## Multi-Application Composition

Every `iris.New` call registers the application in a process-wide registry.
Give applications names through their options and any package can retrieve
them without import cycles:

```go
app := iris.New(iris.Options{Name: "api"})

// Elsewhere, without importing the package that built it:
import "github.com/kataras/iris/v14/apps"

api := apps.Get("api") // nil if no app has that exact name.
all := apps.GetAll()   // every registered *iris.Application.
apps.OnApplicationRegistered(func(app *iris.Application) {
    // fires for each future registration.
})
```

The registry itself lives in the `context` package:
`context.GetApplications()` returns the same set as read-only
`context.Application` interface values; that is what framework internals
use, while `apps.GetAll` casts to the concrete type for you.

### The Switch Application

`apps.Switch` composes several applications behind one listener. It returns
a new, ordinary application whose job is to route each request to the first
matching case:

```go
package main

import (
    "github.com/kataras/iris/v14"
    "github.com/kataras/iris/v14/apps"
)

func newWebsite() *iris.Application {
    app := iris.New(iris.Options{Name: "website"})
    app.Get("/", func(ctx iris.Context) { ctx.Writef("website") })
    return app
}

func newAPI() *iris.Application {
    app := iris.New(iris.Options{Name: "api"})
    app.Get("/status", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"ok": true})
    })
    return app
}

func main() {
    website := newWebsite()
    api := newAPI()

    switcher := apps.Switch(apps.Hosts{
        {Pattern: "api.example.com", Target: api},
        {Pattern: "example.com", Target: website},
    })

    switcher.Listen(":80")
}
```

What `Switch` actually does: it builds each case application on its own
`Build` (via `OnBuild`), then installs a `UseRouter` handler that evaluates
the case filters in registration order. On the first match it forwards the
request to that application's `ServeHTTP`: the matched app serves with its
own context pool and configuration. If nothing matches, the request falls
through to the switcher's own router, so you can register fallback routes or
a custom 404 on the switcher itself. It is deliberately not a load balancer;
it is a filter chain.

The `Hosts` provider matches the request host against each `Pattern`, which
may be a literal or a regular expression (the `apps.AnyDomain` constant
matches any domain). `Target` accepts an `*iris.Application`, the name of a
registered application, or a plain host string; the latter produces a
permanent redirect to that host. Providers combine with `apps.Join`, and the
`apps.SetHost("canonical.example.com")` option rewrites the request host
before the matched application sees it, useful for serving `www.` and the
root domain from one application without redirects.

## Standard Library Interop

Iris converts in both directions: standard handlers into Iris handlers, and
the application itself into a standard `http.Handler`.

`http.Handler` is a one-method interface,
`ServeHTTP(http.ResponseWriter, *http.Request)`, and it is the lingua
franca of Go's HTTP ecosystem: metrics exporters, profilers, proxies
and entire frameworks all either implement it or accept it. That shared
contract is what makes both conversions below mechanical.

Calls in the other direction, out to somebody else's service, have their
own package: `github.com/kataras/iris/v14/client`, whose `client.New`
takes a base URL, persistent request options, timeouts, rate limiters and
a retry policy, and whose request helpers marshal and unmarshal JSON, form
and multipart bodies, so a service does not hand-roll a fourth
`*http.Client` wrapper. `c.BindJSON[Order](ctx, http.MethodGet, "/orders/42", nil)`
names the response type at the call site and returns it, the way
`ctx.BindJSON` does on the server side; a status of 400 or above comes
back as an `APIError` that `rest/errors.HandleAPIError` forwards to your
own client. A paginated Iris list reads back through `client.List[T]`,
one page, or `client.Items[T]`, which walks every page as an
`iter.Seq2[T, error]`; both send QUERY and decode into the same `Page[T]`
the server built. Retries with backoff, a rate limit per client or per endpoint,
and redaction of API keys from error messages and debug dumps are options,
not code you write. The package is a thin layer over the author's
standalone `github.com/kataras/httpclient`, with the framework's JSON
policy as its default, so an application never imports that module
directly.

### Mounting http.Handler Values

`iris.FromStd` accepts an `http.Handler`, an `http.HandlerFunc`-shaped
function, or a middleware-shaped `func(w, r, next http.HandlerFunc)` and
returns an `iris.Handler`:

```go
package main

import (
    // Third-party: the Prometheus client.
    "github.com/prometheus/client_golang/prometheus/promhttp"

    "github.com/kataras/iris/v14"
)

func main() {
    app := iris.New()
    app.Get("/metrics", iris.FromStd(promhttp.Handler()))
    app.Listen(":8080")
}
```

A mounted handler reads route parameters with `r.PathValue(name)` and
the matched route template, such as `/users/{id:uuid}`, from
`r.Pattern`, in all three forms. `r.Pattern` stays empty where no route
has matched yet or none will: in the `UseRouter` slot and in the
built-in 404 and 405 handlers. Anything written against `net/http`
mounts this way; see [Chapter 17](https://iris-go.com/docs/monitoring-observability/) for
the full monitoring setup around that `/metrics` route.

### The Application as an http.Handler

After `Build`, the application satisfies `http.Handler`, so it drops into
any standard server, test harness or third-party listener:

```go
package main

import (
    "log"
    "net/http"

    "github.com/kataras/iris/v14"
)

func main() {
    app := iris.New()
    app.Get("/", func(ctx iris.Context) {
        ctx.JSON(iris.Map{"served_by": "net/http"})
    })

    if err := app.Build(); err != nil {
        log.Fatal(err)
    }

    srv := &http.Server{
        Addr:    ":8080",
        Handler: app, // *iris.Application implements http.Handler.
    }
    log.Fatal(srv.ListenAndServe())
}
```

`app.Listen` and `app.Run` call `Build` for you; call it yourself only when
handing the application to infrastructure you control, as above. This is
also how one Iris application embeds inside another: build the inner app and
forward requests to its `ServeHTTP` from a route of the outer one.

## Built-in Controllers

The `controller` package tree ships ready-made controllers that implement
the same `Routing(r iris.Router)` interface as your own and register the
same two ways:

```go
// Directly on a router:
app.Router("/static", fileserver.New("./assets", fileserver.DefaultOptions))

// Or through the builder's API step (see Chapter 3):
iris.NewBuilder().
    // ...builder steps...
    API("/static", fileserver.New("./assets", fileserver.DefaultOptions)).
    Build().
    Listen(":8080")
```

### File Server

`fileserver.New(fileSystemOrDirectory, options)` serves static files from a
directory path, an `http.FileSystem`, an `embed.FS` or an `fs.FS`. A
directory path follows symbolic links that point outside it; pass
`root.FS()` of an `os.OpenRoot` instead when that matters, and accept
that a link you placed there on purpose stops being served
([Chapter 18](https://iris-go.com/docs/security-best-practices/#static-file-safety)):

```go
package main

import (
    "embed"

    "github.com/kataras/iris/v14"
    "github.com/kataras/iris/v14/controller/fileserver"
)

//go:embed assets
var embeddedAssets embed.FS

func main() {
    app := iris.New()

    app.Router("/static", fileserver.New(embeddedAssets,
        fileserver.Options{
            IndexName:      "index.html",
            ShowList:       true,
            AssetValidator: fileserver.BlockHidden,
        }))

    app.Listen(":8080")
}
```

The options worth knowing: `IndexName` (directory index file), `ShowList`
and `ShowHidden` (directory listings, with `DirList` for a custom renderer),
`SPA` (serve the index instead of 404 for unknown files, as single-page
applications), `Attachments` (download mode with rate limiting), `Cache`
(in-memory caching and pre-compression), `PushTargets` (HTTP/2 push), and
`AssetValidator`, a per-request `func(ctx iris.Context, name string) bool`
gate. `fileserver.BlockHidden` is a ready validator that denies dotfiles
such as `.git`, `.env` and `.htpasswd`. `fileserver.DefaultOptions` is a
sensible starting value.

The package also exports `Handler` (the same serving logic as a plain
`iris.Handler`, no controller), `Favicon(path)` (a cached
`GET|HEAD /favicon.ico` controller) and `WebDAV(handler)` (wraps a
`golang.org/x/net/webdav` handler, third-party, registering all WebDAV
methods on `/{p:path}`).

### API Graph

`apigraph.New` serves an interactive D3.js visualization of every registered
route: paths, methods, handlers, with editor links to each handler's source
line. The graph is built after the application builds, so registration order
does not matter:

```go
import "github.com/kataras/iris/v14/controller/apigraph"

app.Router("/apigraph", apigraph.New(apigraph.Options{Theme: "dark"}))
```

`Options.Theme` is `"light"` (default) or `"dark"`; register custom themes
with `apigraph.RegisterTheme`. Mount it in development builds: it is a map
of your entire API surface.

### Sitemap

A sitemap is an XML file that lists a site's pages for search-engine
crawlers, optionally hinting how often each page changes and how
important it is relative to the rest.

`sitemap.New(domain)` generates `/sitemap.xml` from the registered static
routes at build time:

```go
import "github.com/kataras/iris/v14/controller/sitemap"

app.Router("/", sitemap.New("https://www.example.com"))

app.Get("/", index)
app.Get("/about", about).
    SetLastMod(time.Now()).
    SetChangeFreq("monthly").
    SetPriority(0.8)
app.Get("/internal", internal).ExcludeSitemap()
// GET /sitemap.xml now lists / and /about.
```

The route methods `SetLastMod`, `SetChangeFreq` and `SetPriority` fill the
matching sitemap elements; `ExcludeSitemap` removes a route. Dynamic,
subdomain and offline routes are excluded automatically, and above 50,000
entries the output splits into an indexed set. With the i18n middleware in
place, `WithLocalization` emits translated alternate links per language.

## Summary

- `mvc.UseController[*C]()` generates the five REST routes from a typed
  controller, with DI-resolved construction, nested resources via `With`
  and parent parameters via `mvc.PathParam[T, V]`; the
  `controller/mvc/handler` package exposes the same typed pipeline
  (`Handler`, `WithStatusCode`, `WithPathParameter`) route-by-route, and
  `mvc.Websocket` / `mvc.GRPC` cover the non-REST protocols.
- `iris.NewContextWrapper[T]()` turns a struct embedding `iris.Context`
  into the argument type of your handlers; pool it for reuse with
  `context.NewContextWrapperWithPool`.
- `iris.Patches()` swaps framework internals (JSON and other body writers,
  domain resolution, cookie defaults, filesystem resolution) once, at
  startup, for the whole process.
- `app.Macros()` extends the path parameter language: `RegisterFunc` adds
  functions to existing types, `Register` adds new types, and
  `SetErrorHandler` shapes the 400 response.
- `WrapRouter`, `AddRouterWrapper` and `PrependRouterWrapper` run raw
  `net/http` code before routing; `Downgrade` replaces the router entirely.
- The `apps` package is a process-wide application registry, and
  `apps.Switch` with `apps.Hosts` routes one listener across many
  applications by host pattern.
- `iris.FromStd` mounts any `net/http` handler, and after `Build` the
  application itself is an `http.Handler`.
- `controller/fileserver`, `controller/apigraph` and `controller/sitemap`
  are ready-made controllers for static assets, route visualization and
  sitemap generation.

## Further Reading

- [Go: Getting started with generics](https://go.dev/doc/tutorial/generics):
  the official hands-on introduction to type parameters.
- [Go blog: An Introduction to Generics](https://go.dev/blog/intro-generics):
  why generics exist and how constraints work, from the Go team.
- [Go: http.Handler](https://pkg.go.dev/net/http#Handler): the
  one-method interface behind router wrappers and both interop
  directions.
- [Go: sync.Pool](https://pkg.go.dev/sync#Pool): the recycling
  mechanism behind the pooled context wrapper.
- [MDN: The WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API):
  what the protocol behind `mvc.Websocket` provides.
- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc):
  the gRPC library that `mvc.GRPC` serves through the Iris router.

You have followed Iris from a first `iris.New()` to generic controllers,
typed contexts and framework patches; there is no larger machinery left
hidden.
