I knew Express. I was comfortable with it. So why did I decide to throw myself into Go, a language that made me question everything I thought I knew about backend development? Here's the honest story.
Suprim Khatri
Backend Developer · March 15, 2026
I had a good thing going with Express. I knew how to spin up a REST API, wire up middleware, connect a database, handle auth. It felt natural. So when I kept feeling this pull toward Go, I ignored it for a while.
I told myself backend is backend. Learn the concepts in one language, transfer them to another. That logic made me choose Express over Go when I was first starting out and honestly, it was the right call at the time.
But the itch never went away.
I couldn't give you a perfectly rational answer. I just wanted to. Sometimes that's enough of a reason to start something.
What I knew going in: Go was compiled, fast, used heavily in production at companies like Google, Uber, Cloudflare, and Docker. What I didn't know: it would completely change how I think about code.
Coming from JavaScript, Go felt like someone took away all my toys.
No npm install something-that-does-everything. No magic abstractions. No try/catch. Just you, the language, and a compiler that refuses to let anything slide.
The thing that broke my brain first was pointers.
In JS, you never think about memory. The runtime handles it. In Go, you're suddenly writing *pgxpool.Pool and &todo.ID everywhere and asking yourself wait, what is actually happening here?
var todo models.Todo // build the struct in memory
rows.Scan(&todo.ID) // pass the address so Scan can write into it
todos = append(todos, &todo) // store the address, not a copyIt took me an embarrassing amount of time to understand why you'd do var todo models.Todo instead of var todo *models.Todo. The answer? Scan needs real memory to write into. A pointer to nothing is just an address to an empty lot the postman shows up and there's no house.
Once that clicked, something shifted.
JavaScript hides memory from you. That's a feature it makes the language approachable. But it also means you can spend years writing JS without ever understanding what's actually happening when you pass a value around.
Go makes it explicit. Every time you see * or &, you're making a deliberate decision about memory. It's verbose at first. Then it becomes muscle memory. Then you start appreciating it.
The same thing happened with error handling. No try/catch in Go every function returns an error you have to handle manually:
rows, err := pool.Query(ctx, query)
if err != nil {
return nil, fmt.Errorf("GetAllTodos: %w", err)
}At first it felt like noise. Then I realized: this is actually great. Every error is explicit. Nothing fails silently. You know exactly what can go wrong and where.
I won't sugarcoat this. The JS ecosystem is incredible.
Better Auth, Drizzle, Zod, Resend, the Vercel AI SDK everything is one npm install away and works like magic. In Go, you wire things up yourself. There's no Better Auth equivalent. No Drizzle. Validation is manual switch cases on struct tags.
For a while this felt like a downgrade. Then I realized it was the opposite.
When you use magic libraries, you understand the what but not the why. When you build auth yourself in Go generating JWT tokens, setting httpOnly cookies, handling refresh logic, writing email verification token flows you understand auth at a level that using Better Auth never gave you.
The JS ecosystem's strength is also its weakness: it abstracts so much that you can ship without understanding.
Still learning. Still getting confused. Still occasionally wondering why I didn't just stick with Express.
But then something clicks a pointer makes sense, an error handling pattern feels right, raw SQL stops feeling scary and I remember why I started.
Go is making me a better developer not because it's better than JavaScript, but because it forces me to think. And thinking, it turns out, is the whole point.
If you're a JS developer curious about Go: do it. Give yourself a few weeks of genuine discomfort. The other side is worth it.