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.
Enjoyed this one?
Hit the heart so I know to write more like it.
Keep reading

Six years late, my New Zealand drone footage finally made it into a film
In February 2020 I flew a drone around New Zealand and came home with hours of footage. I never edited it. This week I gave the lot to Claude and HyperFrames and ended up with a full film, a trailer and a stack of vertical clips.

Three days, two lakes and one leaky dry suit
I've wanted to learn to dive since I was a teenager. Three days, one pool, two lakes and an online exam later, I'm a BSAC Ocean Diver. I also lost a mask, discovered a dry suit is not always dry, and noticed my weight belt was loose at 18 metres.

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.