Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/go-chi/chi/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through creating a working HTTP service with chi from scratch. By the end you will have a server running on localhost:3333 that handles requests with structured middleware and dynamic URL parameters. The only prerequisite is a working Go 1.23+ installation and a Go module (go mod init) for your project.

Steps

1

Install chi

Add chi to your Go module. Run the following command inside your project directory:
go get github.com/go-chi/chi/v5
This fetches chi v5 and records it in your go.mod file. chi has zero external dependencies, so nothing else will be pulled in beyond chi itself.
Make sure you have initialized a Go module first. If you haven’t, run go mod init your/module/name before the go get command.
2

Write a hello-world server

Create main.go in your project directory with the following content. This is the canonical hello-world example drawn directly from chi’s own _examples/hello-world/ directory:
main.go
package main

import (
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	http.ListenAndServe(":3333", r)
}
Three built-in middlewares are applied to every request:
  • RequestID — injects a unique request ID into the context and response headers.
  • Logger — logs the start and end of each request with the elapsed processing time.
  • Recoverer — catches any panics in downstream handlers, prints the stack trace, and returns a 500 response instead of crashing the server.
3

Run the server

Start the server with:
go run main.go
You should see the Logger middleware print a line for each incoming request. In a separate terminal, test the endpoint:
curl http://localhost:3333/
Expected response:
hello world
The Logger middleware will output something similar to:
2024/02/16 12:00:00 "GET http://localhost:3333/ HTTP/1.1" from 127.0.0.1 - 200 11B in 42.5µs
4

Add a URL parameter route

Extend your server to handle a dynamic URL segment. chi uses {paramName} syntax in route patterns and provides chi.URLParam to read the value at runtime.
package main

import (
	"fmt"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()
	r.Use(middleware.RequestID)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	// Named URL parameter: {name}
	r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
		name := chi.URLParam(r, "name")
		w.Write([]byte(fmt.Sprintf("hello, %s!", name)))
	})

	http.ListenAndServe(":3333", r)
}
chi.URLParam(r, "name") reads the {name} segment from the URL. The value is stored on the request’s context.Context by chi’s router — no global state is involved.
5

Explore a richer example

For a more complete picture, here is a server with route groups, a middleware stack, and a Timeout. This pattern mirrors real-world chi services:
main.go
package main

import (
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

func main() {
	r := chi.NewRouter()

	// Base middleware stack applied to all routes
	r.Use(middleware.RequestID)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)
	r.Use(middleware.Timeout(60 * time.Second))

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hi"))
	})

	// Route group for /articles with its own middleware
	r.Route("/articles", func(r chi.Router) {
		r.Get("/", listArticles)       // GET /articles
		r.Post("/", createArticle)     // POST /articles
		r.Get("/search", searchArticles) // GET /articles/search

		// Nested route with middleware applied inline
		r.Route("/{articleID}", func(r chi.Router) {
			r.Use(articleCtx)
			r.Get("/", getArticle)     // GET /articles/123
			r.Put("/", updateArticle)  // PUT /articles/123
			r.Delete("/", deleteArticle) // DELETE /articles/123
		})
	})

	http.ListenAndServe(":3333", r)
}

func listArticles(w http.ResponseWriter, r *http.Request)   { w.Write([]byte("list")) }
func createArticle(w http.ResponseWriter, r *http.Request)  { w.Write([]byte("create")) }
func searchArticles(w http.ResponseWriter, r *http.Request) { w.Write([]byte("search")) }
func getArticle(w http.ResponseWriter, r *http.Request)     { w.Write([]byte("get")) }
func updateArticle(w http.ResponseWriter, r *http.Request)  { w.Write([]byte("update")) }
func deleteArticle(w http.ResponseWriter, r *http.Request)  { w.Write([]byte("delete")) }

func articleCtx(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		articleID := chi.URLParam(r, "articleID")
		// load article from DB, attach to context, etc.
		_ = articleID
		next.ServeHTTP(w, r)
	})
}
Key patterns demonstrated here:
  • r.Route(...) creates an inline sub-router at a path prefix with its own middleware stack.
  • Middleware added inside r.Route(...) only runs for requests matching that group.
  • middleware.Timeout signals through ctx.Done() when the deadline is exceeded, allowing handlers to abort cleanly.

What’s next?

Routing Overview

Deep dive into URL patterns, wildcards, regexp params, and sub-router mounting.

Middleware Overview

Browse chi’s full built-in middleware suite and learn how to write your own.

REST API Guide

Build a complete CRUD REST API with chi, including context-based resource loading.

Installation

Go module setup, version requirements, and upgrading from chi v4.

Build docs developers (and LLMs) love