2 min read
Minimal APIs should be minimal
The best API endpoint does almost nothing. How keeping endpoints minimal — and finessing it with the Result pattern at Covertime — made every handler read the same way.

The best API endpoint does almost nothing. Take a request, maybe validate it, call into the application layer, hand back what it returns. That's the whole job.
I first ran into this at a previous company. The rule we set ourselves was simple: keep the endpoints minimal. An endpoint takes in a request, maybe runs some validation or a bit of mapping, calls into the application layer where the actual logic lives, and then — based on the result — returns either a problem or a success. Nothing more.
We cared because we'd lived with the opposite. We had endpoints where a lot of the logic had crept in, calling into several different application layers, branching all over the place. They were bloated, hard to read, and every so often genuinely hard to debug — because the thing that was meant to be a thin entry point had quietly become the place where all the work happened.
The endpoint's only job is to translate#
HTTP comes in, HTTP goes out. Everything in between belongs somewhere else. The application layer owns the logic and, crucially, owns the outcome — it decides whether the thing succeeded. The endpoint's job is just to turn that outcome into a status code. The moment an endpoint starts deciding things, it's doing someone else's job.
Finessing it with the Result pattern#
At Covertime we took it further. Instead of the application layer throwing for expected failures, it returns a Result — either a success value, or a populated error the endpoint can hand straight back as ProblemDetails. “Couldn't find it”, “not allowed”, “that's already been used” aren't exceptional — they happen constantly, by design — so they shouldn't travel as exceptions. They're just the other half of the return type.
app.MapPost("/quotes", async (CreateQuoteRequest request, IQuoteService quotes) =>
{
var result = await quotes.Create(request.ToCommand());
return result.IsSuccess
? Results.Ok(result.Value)
: Results.Problem(result.Error); // a populated ProblemDetails
});Clean and consistent, every time#
The payoff is that every endpoint now reads exactly the same way. A new engineer can open any handler in the codebase and know its shape before they've read a line: request in, delegate down, translate the result out. Nothing hiding, nothing to untangle at 3am.
And exceptions go back to meaning what they should — something genuinely went wrong — instead of being control flow for outcomes we already knew were coming.
Keep reading

£1,330 a year off three clients' bills
I kept telling people I save clients money without ever putting a number on it. So I built a running total that works itself out daily, and made it show where every line came from.

WAKOOS Centre4Children is live
WAKOOS Centre4Children is up: a charity nursery in Billingshurst, off an ageing drag-and-drop site builder and onto Next.js on Cloudflare. £781 a year of hosting became about £20, and the £20 is just the domain names.

Why I built this site
Almost none of the developers I interview have a website. That's most of the reason this one exists, plus a hedge against a harder job market, and a bit of fun.