Iris is a web framework for the Go programming language, built for HTTP
APIs and web applications. It gives you a fast router with typed path
parameters, a single request/response context with safe accessors,
dependency injection for wiring services into handlers, and a set of
production middleware (recovery, compression, CORS, access logging) that
assemble into one coherent stack. The module path is
github.com/kataras/iris/v14 and it requires Go 1.27 or newer. This
chapter installs Iris, builds a first application, and previews the
production pattern the rest of the book expands on.
Background
If Iris is your first web framework, or Go your first server-side language, this section explains the moving parts before you install anything. If you have built HTTP services before, skip to Installation.
What a server does. A web application is a program that never
exits: it listens on a network port (an address such as :8080), waits
for clients (browsers, mobile apps, curl, other services) to
connect, and answers each request they send. One request in, one
response out; HTTP is that simple loop repeated millions of times.
What a request and a response look like. Both are short structured
messages. A request carries a method (a verb such as GET for reading
or POST for submitting data), a path (/orders/42), headers
(key-value metadata such as Accept: application/json), and sometimes
a body (the submitted data itself). The response carries a status code
(a three-digit result: 200 success, 404 not found, 500 server
error), its own headers, and a body: the HTML, JSON or file the client
asked for. Every feature in this book manipulates some part of these
two messages.
What the framework does for you. Go’s standard library can already
serve HTTP. A framework adds the layer you would otherwise write by
hand for every project: routing (deciding which of your functions runs
for GET /orders/42 versus POST /orders), parsing (turning raw
request text into typed Go values you can trust), responding (turning
your Go values back into correct HTTP with the right status code and
content type), and the shared machinery around them: logging, crash
recovery, security headers. The problem that disappears from your code
is plumbing: you write the part that is unique to your application, and
the framework supplies the part that is the same in every application.
What a Go module is. Go installs dependencies per project, not
system-wide. A module is a folder with a go.mod file that names your
project and pins the exact version of every library it uses.
go mod init myapp creates that file; go get adds a dependency to it
and downloads the code. There is no separate package manager to install
and no virtual environment to activate. The go tool you already have
does all of it.
The examples below use curl, a command-line tool (preinstalled on
most systems) that sends an HTTP request to a URL and prints the
response, so you can poke at your server without a browser.
Installation
You need Go 1.27+ and basic familiarity with the language (the Go Tour covers everything this book assumes). Create a module and add Iris to it:
mkdir myapp && cd myapp
go mod init myapp
go get github.com/kataras/iris/v14@latestThat is the entire setup. Iris is a regular Go module dependency: no code generators, no CLI tools, no configuration files required to start.
Your First Application
Create main.go:
package main
import "github.com/kataras/iris/v14"
func main() {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.WriteString("Hello, Iris!")
})
app.Get("/greet/{name:string}", func(ctx iris.Context) {
name := ctx.Params().GetString("name")
ctx.JSON(iris.Map{"message": "hello, " + name})
})
app.Get("/orders/{id:int}", func(ctx iris.Context) {
id := ctx.Params().GetInt("id")
ctx.JSON(iris.Map{"order": id, "status": "pending"})
})
app.Listen(":8080")
}Run it and try the routes:
go run main.go
curl http://localhost:8080/ # Hello, Iris!
curl http://localhost:8080/greet/iris # {"message":"hello, iris"}
curl http://localhost:8080/orders/42 # {"order":42,"status":"pending"}
curl http://localhost:8080/orders/abc # 404: "abc" is not an intThree things are happening here.
iris.New() returns an *iris.Application with sensible defaults. Route
registration methods (Get, Post, Put, Delete, Patch and the
rest) hang directly off the application, and each takes a path pattern
plus one or more handlers.
Every handler receives an iris.Context, the single object you use to
read the request and write the response. ctx.WriteString writes a plain
string body; ctx.JSON serializes any Go value (here iris.Map, a
shorthand for map[string]any) and sets the content type.
The path patterns carry typed macros. {name:string} matches any single
path segment; {id:int} matches only integers, so a request for
/orders/abc never reaches your handler. The most useful macros:
| Macro | Matches |
|---|---|
{name:string} | any single path segment |
{id:int} | integers |
{id:uuid} | UUID v1/v4 |
{ok:bool} | true/false, 1/0 |
{email:email} | e-mail addresses |
{path:path} | the rest of the path, any depth |
Chapter 4 covers the full macro language,
route grouping with app.Router and the controller pattern.
Reading Parameters
Iris exposes request input through typed accessors with safe defaults.
There are no (value, err) pairs to unpack for simple reads: a missing
string is "", a missing or malformed number is -1.
Path parameters come from ctx.Params():
app.Get("/users/{id:int}", func(ctx iris.Context) {
id := ctx.Params().GetInt("id") // already validated by the macro.
ctx.JSON(iris.Map{"user_id": id})
})Query string values come from ctx.Query() the same way:
app.Get("/search", func(ctx iris.Context) {
q := ctx.Query().GetString("q")
page := ctx.Query().GetInt("page")
if page < 1 {
page = 1 // GetInt returns -1 when the key is absent.
}
ctx.JSON(iris.Map{"query": q, "page": page})
})Because the macro already validated {id:int}, the handler can trust the
value. When you need richer input handling (JSON bodies, forms, headers,
validation), Chapter 5 covers it in depth.
Running the Server
app.Listen(addr) starts an HTTP server on the given address and blocks
until the server stops. On SIGINT/SIGTERM it stops accepting new
connections and drains in-flight requests gracefully; you do not write
any shutdown code for the common case. (SIGINT and SIGTERM are the
shutdown signals your process receives when you press Ctrl+C or when a
container platform stops it.)
If the server cannot start, because the port is taken or a route pattern is malformed, Iris logs the failure and the process exits with status 1. That is why the call takes no error check: a graceful stop returns nil and a failed start never returns at all. Chapter 19 covers the opt-out for a process that runs more than one server.
When you need more than an address, pass iris.ServerOptions to
app.Run. TLS is the everyday reason. TLS (Transport Layer Security)
is the encryption layer that turns http:// into https://: it keeps
traffic private and proves to clients that they are talking to your
server, not an impostor. It requires a certificate: a file, signed by
a trusted authority, that ties your domain name to your server. Let’s
Encrypt is a free certificate authority that issues and renews such
certificates automatically:
// Manual certificates:
app.Run(iris.ServerOptions{
Addr: ":443",
CertFile: "cert.pem",
KeyFile: "key.pem",
})
// Automatic certificates from Let's Encrypt:
app.Run(iris.ServerOptions{
Addr: ":443",
Letsencrypt: true,
Hosts: []string{"example.com"},
})app.Listen(":8080") is app.Run with a small set of defaults filled in
for you: a TCP network, a three-minute keep-alive, socket sharding and the
application’s log writer. The options struct also covers TLS, timeouts and
tunneling; Chapter 19 walks through the
production serving choices.
A First Taste of the Production Stack
iris.New() plus hand-registered routes is fine for small programs and
for learning. For real services, Iris ships a builder that assembles the
recommended stack: a compiler-enforced chain of steps that configures
CORS, compression, logging, a health endpoint, centralized error
handling and dependency injection, then mounts your API controllers.
Three of those terms get full chapters later but deserve one line now. Middleware is code that runs before or after every handler: the place for logging, panic recovery and security checks, written once instead of in each handler. CORS (Cross-Origin Resource Sharing) is the browser permission system that decides which web pages served from other domains may call your API. Dependency injection means your constructors declare what they need (a database, a logger) and the framework supplies it, instead of your code reaching for global variables.
A controller in this style is a plain struct with a constructor and a
Routing(r iris.Router) method. Here is the smallest complete example:
package main
import (
"github.com/kataras/iris/v14"
)
// GreetAPI is a controller: a constructor that declares its dependencies
// (none yet) and a Routing method that registers its routes.
type GreetAPI struct{}
func NewGreetAPI() *GreetAPI { return &GreetAPI{} }
func (api *GreetAPI) Routing(r iris.Router) {
r.Get("/{name:string}", api.greet)
}
func (api *GreetAPI) greet(ctx iris.Context) {
name := ctx.Params().GetString("name")
ctx.OK(iris.Map{"message": "hello, " + name}, nil)
}
func main() {
app := iris.NewBuilder().
Prefix("/api").
AllowOrigin("none").
Compression(true).
LogRequests(false).
Health(true, "development", "you@example.com").
Services().
RouterMiddlewares().
Middlewares().
API("/greet", NewGreetAPI).
Build()
app.Listen(":8080")
}curl http://localhost:8080/api/greet/iris # {"message":"hello, iris"}
curl http://localhost:8080/health # health report, at the rootEach step returns the interface of the next, so the compiler enforces the
order; the two middleware steps stay in the chain even when you pass them
nothing. RouterMiddlewares() installs recovery and the framework’s
error handling for you. The ctx.OK(value, err) helper renders the
value as 200 JSON, and hands any non-nil error to a central error map you
configure once at startup, which is what keeps production handlers this
short.
As the application grows, the same chain absorbs the real work:
Services(...) registers repositories and services for injection into
constructors like NewGreetAPI, and Errors(...) maps your domain
errors to HTTP status codes in one place. Two references take you there:
TUTORIAL.mdat the repository root builds a complete, tested service on this stack step by step (DI, error mapping, pagination, httptest).- Chapter 3 documents every builder step in depth.
Project Layout
Start small: one main.go is a perfectly good layout for the first day.
When the program grows past a few handlers, split it into the shape the
tutorial uses, three layers with one direction of dependency:
myapp/
├── go.mod
├── main.go # configuration, wiring, startup
├── main_test.go # HTTP tests against the in-memory application
├── api/ # HTTP layer: controllers and thin handlers
│ └── greet.go
└── catalog/ # your domain package: entities, rules, storage
├── catalog.go
└── service.goName the domain package after your domain (the tutorial’s Bookshelf
service calls it library). The rule that keeps the layout healthy: api
imports the domain package, and the domain package imports nothing from
HTTP. That separation is what makes the service testable with plain Go
interfaces later, as Chapter 16 shows.
Summary
- Iris installs as a normal Go module:
go mod init, thengo get github.com/kataras/iris/v14@latest. iris.New()creates an application;app.Get(path, handler)and friends register routes; handlers receive aniris.Context.- Path patterns carry typed macros such as
{name:string}and{id:int}that validate before your handler runs. - Read input with
ctx.Params().GetString/GetIntandctx.Query().GetString/GetInt; missing values return safe defaults ("",-1), not error tuples. app.Listen(addr)serves HTTP with graceful shutdown built in;app.Run(iris.ServerOptions{...})adds TLS, Let’s Encrypt and server tuning.- The recommended production stack is
iris.NewBuilderwith controllers and the response helpers; the framework’sTUTORIAL.mdis the full hands-on walkthrough and Chapter 3 covers the builder in depth. - Organize growing code as
main.goplus anapipackage and a domain package, with dependencies pointing only toward the domain.
Further Reading
- MDN: An overview of HTTP: the best first read on how the request/response cycle actually works.
- MDN: HTTP messages: the exact structure of requests and responses. Method, path, headers, status line, body.
- Managing dependencies (go.dev):
the official guide to Go modules,
go getandgo.mod. - A Tour of Go: the interactive introduction to the Go language this book assumes.
- Writing Web Applications (go.dev): serving HTTP with only the standard library; useful for seeing exactly what a framework saves you.