Web framework for Go
Every step exposes only what is still legal.
Iris assembles an application through eight interfaces. Each one hands you the next, and each one is narrower than the last. A step called out of turn is a compile error, not something you find in production on a Friday.
go get github.com/kataras/iris/v14@latestGo 1.27 or newer. BSD-3-Clause. Free, and always has been.
The compiler is the first reviewer
What the chain looks like, and what it refuses
On the left, a production application in one expression. On the right, the same chain with one step out of order. Nothing runs. Nothing gets as far as a test.
Builds
iris.NewBuilder().
Prefix("/api").
AllowOrigin("*").
Compression(true).
LogRequests(true).
Health(true, "production", "kataras").
Errors(errors.NewOptions().
MapErrors(errors.NotFound, catalog.ErrNotFound)).
Services(catalog.NewRepository, catalog.NewService).
RouterMiddlewares().
Middlewares().
API("/products", api.NewProductsAPI).
Build().
Listen(":8080")CORS, compression, access logs, a health endpoint, the error map, your services and your API groups. One expression, checked at compile time.
Does not build
iris.NewBuilder().
Prefix("/api").
API("/products", api.NewProductsAPI). // not yet
Build()$ go build ./...
./main.go:12:35: iris.NewBuilder().Prefix("/api").API undefined
(type iris.CorsBuilder has no field or method API)Prefix returns CorsBuilder, which exposes AllowOrigin and Cors. There is no API on it to call.
Streaming, and the AI backends built on it
The parts of a live response that break in production
Server-Sent Events are three lines of Fprintf until something buffers them, a write deadline ends the answer mid-sentence, or a client disappears without closing the connection and the work runs on for nobody. The sse package handles those, and ai puts a model's output on top of it. The error branch is rest/errors.HandleError, the same one every other handler uses.
- Nothing commits until the first
Send. An authorization check that fails afterOpenstill returns an ordinary status code with an ordinary body. - That first event unbuffers the response. Compression and the recorder an access log installs step aside, the absolute
WriteTimeoutis cleared, and the headers that stop a proxy buffering go out. - A 15s heartbeat notices a client that vanished. Without a write that fails, a closed lid stays invisible for about fifteen minutes of TCP retransmission.
- One handler serves both AI shapes. Streaming when the request asks for it, one JSON document when it does not, with nothing in between changing.
stream, err := sse.Open(ctx) // writes nothing yet.
if errors.HandleError(ctx, err) {
return
}
defer stream.Close(nil) // the terminal frame, on success too.
for token := range tokens {
if err := stream.Send(sse.Event{Name: "token", Data: []byte(token)}); err != nil {
return // the client is gone.
}
}A model's output
sse.Read·ai.Classify·ai.RelayYour loop, or a provider stream relayed as it arrives. A failure gets a class before anything is written: fatal, transient, rate limited, exhausted, canceled.
aithe vocabularytext · reasoning · tool · tool-result · usage · error · data
Seven part kinds and three encoders (SSEJSON, NDJSON, PlainText), none of them a vendor schema. Text and reasoning deltas coalesce at 100ms.
ssethe transportOpen · Send · Close
Open writes nothing. The first Send commits and unbuffers the response, a 15s heartbeat notices a client that vanished, and Close writes
doneon success too.One endpoint, two answers
text/event-stream· one JSON documentOptions.Stream is a *bool. Left nil, the Accept header decides. Nothing between Open and Close changes.
ai, the vocabulary, from sse, the transport.iris.NewBuilder
Eight interfaces, each narrower than the last
The builder does not hand back one object with forty methods on it. Each step returns a different interface, and that interface exposes only the steps that are still legal. Follow it down and the aperture closes.
The chain in the framework's own words: Prefix or NoPrefix, AllowOrigin, Compression, LogRequests, Health or NoHealth, Errors, Deferrables, Services, Middlewares, API, Build.Errors, Deferrables, RouterMiddlewares and API return their own interface, so those can repeat.
And then handlers get short
Decoding, validation and error rendering happen once
Configure them at the top of the application and a handler stops repeating them. This one is six lines because the other twenty live somewhere else, in one place, where you can change them all at once.
func (api *ProductsAPI) create(ctx iris.Context) {
input, ok := ctx.BindJSON[catalog.ProductInput]()
if !ok {
return // 400 already written: malformed JSON or failed Validate.
}
id, err := api.svc.Create(ctx, input)
ctx.Created(id, err) // 201, or the centrally mapped error.
}Batteries included
The parts you would otherwise assemble yourself
Routing that rejects bad input first
Typed path parameters with 20 built-in types, plus your own macros and constraints. A request that does not match the type never reaches your handler.
One place for errors
Canonical error codes, one wire format, and a single map from Go error to HTTP response. Handlers return errors; the map decides what a client sees. Internals do not leak.
Dependency injection that stays testable
Constructors declare what they need and the container provides it. Startup hooks run before the first request, and closers run on shutdown without you wiring either.
Payloads that validate themselves
Give a type a Validate method and ctx.BindJSON runs it after decoding. A handler that gets a value can trust the value.
Tests with no ports and no sleeps
The httptest helpers run your real router, your real middleware chain and your real error map in process. No network, no waiting, no flakes.
Secure defaults you did not have to find
Session cookies ship Secure and SameSite=Lax. JWT extraction is header-only. Wildcard CORS will not carry credentials. The defaults are the hardened ones.
In the tree, not in your go.mod
31 middleware packages ship with the framework
Every one of these is versioned with Iris and tested against it. The builder installs several of them for you; the rest are one import away.
accesslogapikeybasicauthbodylimitcachecompresscorscountergeolocationgrpchcaptchahttpcosti18nipaccessjwtmethodoverridemodrevisionmonitorpprofraterecaptcharecorderrecoveryreferrerrequestidrewriteservertimingsessionsversioningviewwebsocket
Compression covers gzip, deflate, br, snappy, s2 and zstd. What each one does, and 78 runnable examples in the repository.

Fifth Edition · 438 pages
The documentation is a book, and the book is free
24 chapters from a first route to a tested application in production. Written against 14.0.0, with every example verified in the framework source rather than remembered.
No registration, no email address and no payment. If it helps you, sponsoring the project is what keeps it maintained.
Who stands behind it
Ten years in the open
Iris has been developed in public since 2016, funded by the people who use it. The record is all here: who backs it, every commit, and every issue ever filed.
Sponsors
Everyone who has put money into the project, listed by name.
The source
Every commit, every release, every open issue and every closed one.
Code Wiki
Google generates an AI wiki from this repository and keeps it in sync, commit by commit.
Curious about speed? We benchmark Iris against other frameworks in the open, at kataras/server-benchmarks. The harness, the handlers and the raw results are all in that repository, so you can read the method and run it on your own hardware.