DEV Community

Brender Adhiambo
Brender Adhiambo

Posted on

Why I Chose Chi for My Go Backend

When I started building my Go backend, one of the decisions I had to make was which router to use.

There are quite a few options in the Go ecosystem, but I decided to go with Chi.

I had used Go for smaller projects before, but building a larger backend made me realize that choosing the right tools is just as important as writing the code itself.

I wasn't looking for something that would build the whole application for me. I wanted something that would handle routing and give me enough flexibility to decide how the rest of my backend should be structured.

That's where Chi came in.

Why Chi?

The first thing I liked about Chi was its simplicity.

Chi doesn't try to do everything for you. It focuses mainly on routing and middleware, while allowing you to structure the rest of your application the way you want.

For my project, that was exactly what I needed.

A simple route can look like this:

r := chi.NewRouter()

r.Get("/users", getUsers)
r.Post("/users", createUser)

There isn't much happening here, and that's actually what I liked about it.

The route tells me exactly what it does. A GET request goes to one handler, while a POST request goes to another.

As I worked on the backend, I started understanding these things more clearly instead of just copying patterns from examples.

That was important for me because I wasn't only trying to make the application work. I was also trying to understand why it was structured that way.

Working With Middleware

Another part of Chi that I found useful was middleware.

When I first started working with backend applications, I mostly thought about routes and handlers. As the project became bigger, I realized there was a lot of functionality that didn't belong inside individual handlers.

Things like logging, authentication, recovering from panics, and checking requests are good examples.

Chi made it easy to apply middleware to my routes.

For example:

r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

This helped me understand an important backend concept: not everything needs to live inside the handler.

Middleware sits between the incoming request and the handler. It can perform some work before the request reaches the handler and, depending on the middleware, also work with the response afterward.

That changed how I thought about structuring my backend.

Instead of putting everything into one function, I could separate responsibilities and make each part easier to understand.

Route Groups

As I added more endpoints, I also started organizing routes into groups.

For example:

r.Route("/api", func(r chi.Router) {
r.Route("/users", func(r chi.Router) {
r.Get("/", getUsers)
r.Post("/", createUser)
})
})

This became more useful as the application grew.

Having all the routes in one large list can quickly become difficult to maintain. Grouping them allowed me to organize endpoints around different parts of the application.

It also made the router easier to read.

When I looked at my routes, I could get a quick idea of how the API was organized without having to search through the entire application.

Chi and My Backend Structure

One of the biggest things I learned while using Chi was that the router is only one part of a backend.

It is easy to think that building an API is mostly about creating endpoints, but there is much more happening behind each request.

A request can go through middleware, reach a handler, be validated, interact with the database, and eventually return a response.

Working with Chi made me pay more attention to these boundaries.

I started thinking more carefully about questions like:

What should the router be responsible for?
What belongs in the handler?
Where should validation happen?
Where should database logic live?
Which functionality should be middleware?
How should different parts of the API be organized?

These questions became more important as my project grew.

Instead of putting everything into one place just because it worked, I started thinking about how I could keep the code easier to maintain.

What I Learned

Using Chi taught me more than just how to create routes.

It helped me understand how a Go backend is structured and how different parts of a request move through an application.

I became more comfortable working with:

HTTP methods
Routes and handlers
Middleware
Route groups
API structure
Request handling
Separation of responsibilities

I also learned that a framework doesn't need to be complicated to be useful.

Before using Chi, I sometimes assumed that a more feature-heavy framework would automatically make backend development easier.

My experience was a little different.

Having a lightweight router meant I had to understand more of what I was building. At first, that meant I had more questions. But those questions were actually useful because they pushed me to understand the fundamentals instead of depending on the framework to make decisions for me.

Looking Back

Choosing Chi was one of the decisions that made building my Go backend easier.

It gave me the routing and middleware tools I needed without forcing me into a large framework structure.

More importantly, working with it helped me understand something I didn't fully appreciate when I started: good backend development isn't only about making endpoints work.

It's also about knowing where different responsibilities belong and keeping the code understandable as the project grows.

As I continue working with Go, I'm learning that the tools you choose can influence how you think about and structure your application.

For me, Chi was a good fit because it stayed out of the way and let me focus on building the backend.

And honestly, that's one of the things I appreciate most about Go in general.

Top comments (0)