<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Backend on</title><link>/tags/backend/</link><description>Recent content in Backend on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 20 Jun 2025 00:00:00 +0000</lastBuildDate><atom:link href="/tags/backend/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 10: The Complete Go Service — HTTP + DB + workers + shutdown in 200 lines</title><link>/post/go/go-api-complete-service/</link><pubDate>Fri, 20 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-complete-service/</guid><description>&lt;p&gt;Every lesson in this series has been a piece of a puzzle. HTTP routing, middleware, validation, error responses, pagination, idempotency, timeouts, rate limiting, config management — each one addresses a specific concern in isolation. This final lesson puts them together into a single, production-shaped service that you can actually use as a starting point.&lt;/p&gt;
&lt;p&gt;The goal is not a complete application. It is a skeleton that demonstrates how all the pieces wire together in &lt;code&gt;main.go&lt;/code&gt; and what a well-structured Go service looks like before you add your business logic.&lt;/p&gt;</description></item><item><title>Lesson 9: Config Management — Twelve-factor or twelve headaches</title><link>/post/go/go-api-config/</link><pubDate>Sat, 10 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-config/</guid><description>&lt;p&gt;I have seen configuration managed in at least ten different ways across the Go projects I have worked on: hardcoded constants, config structs passed around by pointer, global variables read at startup, YAML files baked into Docker images, environment variables with no validation, and one particularly creative approach involving a shared Google Sheet. Every one of those had the same core problem: the configuration was not treated as a first-class part of the application.&lt;/p&gt;</description></item><item><title>Lesson 8: Rate Limiting — Say no before you break</title><link>/post/go/go-api-rate-limiting/</link><pubDate>Sat, 15 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-rate-limiting/</guid><description>&lt;p&gt;A service I was running had no rate limiting. One night, a script that a client was testing started sending requests in a tight loop — about 4,000 requests per second. The database connection pool saturated within seconds. Other clients started seeing timeouts. By the time I woke up and deployed a fix, we had been degraded for forty minutes. A single &lt;code&gt;429 Too Many Requests&lt;/code&gt; response would have stopped the script cold in seconds.&lt;/p&gt;</description></item><item><title>Lesson 7: Timeouts and Retries — Your client needs a deadline, your server needs a budget</title><link>/post/go/go-api-timeouts-retries/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-timeouts-retries/</guid><description>&lt;p&gt;I once watched a service die in slow motion. An upstream dependency started responding slowly — not failing, just slow. Within minutes, all request goroutines were blocked waiting for the upstream. New requests kept arriving. Goroutines piled up. Memory climbed. Eventually the process was killed by the kernel. The upstream recovered in about thirty seconds. My service was down for twelve minutes. Every second of that outage was caused by the absence of a single line: a timeout.&lt;/p&gt;</description></item><item><title>Lesson 6: Idempotency in APIs — Every POST should be safe to retry</title><link>/post/go/go-api-idempotency/</link><pubDate>Sun, 08 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-idempotency/</guid><description>&lt;p&gt;A client sends a payment request. The network times out. The client does not know if the payment was processed. Should it retry? If it retries and the payment already went through, the customer gets charged twice. If it does not retry and the payment failed, the order never ships. Without idempotency, there is no safe answer to this question.&lt;/p&gt;
&lt;p&gt;This is not a theoretical concern. It happens every time a mobile app retries a failed request, every time a message queue delivers a message at least once, every time a load balancer retries on a 503.&lt;/p&gt;</description></item><item><title>Lesson 5: Pagination Done Right — Offset pagination breaks at scale</title><link>/post/go/go-api-pagination/</link><pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-pagination/</guid><description>&lt;p&gt;I have shipped offset-based pagination more times than I would like to admit. It looks clean, it is trivially easy to implement, and it works perfectly — until the table reaches about 100,000 rows. Then it starts to slow down, and by a million rows it is actively harmful. I had a support dashboard that froze every time someone clicked to page 47. That was the moment I stopped using &lt;code&gt;OFFSET&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 4: Error Responses — Your API errors are your documentation</title><link>/post/go/go-api-error-responses/</link><pubDate>Fri, 20 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-error-responses/</guid><description>&lt;p&gt;I once integrated with an API that returned HTTP 200 for everything — successes and failures alike. The actual outcome was buried in a &lt;code&gt;status&lt;/code&gt; field inside the JSON body. To know whether a request worked, you had to parse the response, check &lt;code&gt;status&lt;/code&gt;, then switch on a string value that was inconsistently named across endpoints. Integrating with that API felt like defusing a bomb in the dark.&lt;/p&gt;
&lt;p&gt;How you design error responses is not a detail. It is part of your public interface.&lt;/p&gt;</description></item><item><title>Lesson 3: Request Validation — Never trust the caller</title><link>/post/go/go-api-validation/</link><pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-validation/</guid><description>&lt;p&gt;I once shipped an endpoint that accepted a &lt;code&gt;limit&lt;/code&gt; query parameter for pagination. The valid range was 1–100. I did not validate it. Someone called it with &lt;code&gt;limit=-1&lt;/code&gt; and my database query returned every row in the table. The query took 45 seconds and brought the service to its knees. The fix was a two-line bounds check that I should have written from the start.&lt;/p&gt;
&lt;p&gt;Validation is not optional. It is the contract between your API and the outside world.&lt;/p&gt;</description></item><item><title>Lesson 2: Middleware Patterns — Wrap the handler, not the logic</title><link>/post/go/go-api-middleware/</link><pubDate>Fri, 05 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-middleware/</guid><description>&lt;p&gt;The first time I wrote authentication logic, I put it directly inside each handler. Copy-paste, then copy-paste again. By handler number five I had four slightly different versions of the same JWT check scattered across the codebase. When the security team asked me to add a new validation step, I had to find and update every single one. That was the day I understood middleware.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Business logic in a handler should answer one question: &amp;ldquo;What does this endpoint do?&amp;rdquo; But handlers inevitably accumulate cross-cutting concerns — logging, authentication, rate limiting, request ID injection, panic recovery. When these live inside the handler body, every handler becomes a tangle of concerns that are hard to test, hard to change, and impossible to apply consistently.&lt;/p&gt;</description></item><item><title>Lesson 1: Designing HTTP APIs in Go — net/http is enough</title><link>/post/go/go-api-http-design/</link><pubDate>Wed, 12 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-http-design/</guid><description>&lt;p&gt;I spent the first few months of writing Go services reaching straight for a framework. Chi, Gorilla, Echo — I cycled through them trying to find the &amp;ldquo;right&amp;rdquo; one. Then a colleague reviewed my code and asked a simple question: &amp;ldquo;What does this framework give you that &lt;code&gt;net/http&lt;/code&gt; doesn&amp;rsquo;t?&amp;rdquo; I couldn&amp;rsquo;t answer. That conversation changed how I think about HTTP in Go.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most developers coming from Node.js or Python assume they need a framework to build a real HTTP API. Express, FastAPI, Django — these tools abstract away so much of the protocol that going back to the raw standard library feels like stepping down. But Go&amp;rsquo;s &lt;code&gt;net/http&lt;/code&gt; package is not an afterthought. It was designed with production use in mind, and the gap between it and popular frameworks is much smaller than you&amp;rsquo;d expect.&lt;/p&gt;</description></item></channel></rss>