Chapter 1

Getting Started

Install Iris v14, write a first route, read a typed path parameter and run the server. Ends with a first taste of the production stack and a project layout.

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:

Shell
mkdir myapp && cd myapp
go mod init myapp
go get github.com/kataras/iris/v14@latest

That 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:

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:

Shell
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 int

Three 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:

MacroMatches
{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():

Go
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:

Go
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:

Go
// 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:

Go
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")
}
Shell
curl http://localhost:8080/api/greet/iris  # {"message":"hello, iris"}
curl http://localhost:8080/health          # health report, at the root

Each 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.md at 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.go

Name 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, then go get github.com/kataras/iris/v14@latest.
  • iris.New() creates an application; app.Get(path, handler) and friends register routes; handlers receive an iris.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/GetInt and ctx.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.NewBuilder with controllers and the response helpers; the framework’s TUTORIAL.md is the full hands-on walkthrough and Chapter 3 covers the builder in depth.
  • Organize growing code as main.go plus an api package and a domain package, with dependencies pointing only toward the domain.

Further Reading

Twenty-five chapters, about 85,000 words. Try BindJSON, graceful shutdown or error map.