Most of what gets written about serverless is about scale. Cold starts at p99, concurrency limits, fan-out, how some company served a billion requests without a server in sight. Fine. But that's not why I love these things.
I love them for the opposite reason. The function that does one small thing. Thirty lines, a URL, done. No repo, no Dockerfile, no CI pipeline, no terraform plan you read three times before running. You have an idea on a Tuesday and by lunch it's a live endpoint you can curl. That's the part nobody puts on a conference slide.
So here's a little homage to that. We're going to build a serverless QR code API. It's genuinely about 30 lines, it's live in under a minute, and by the end you'll have a URL that turns any text into a scannable QR.
The whole thing
A function on orkestr Functions is a single handler. The runtime hands you an event (method, path, query, headers, body), you return a dict. If that dict has a statusCode, it's treated as a full HTTP response, and the body can be raw bytes. Which means a function can return a PNG and the browser just renders it.
That last detail is the whole trick. Here's the handler:
import io
import segno
def handler(event):
data = event["query"].get("data", "https://orkestr.eu").strip()
# Bare domains like "orkestr.eu" decode as plain text on phones and do
# nothing when scanned. Promote anything without a scheme to https so the
# QR is always tappable.
if data and "://" not in data and not data.startswith(("mailto:", "tel:", "wifi:")):
data = "https://" + data
buf = io.BytesIO()
segno.make(data, error="m").save(buf, kind="png", scale=10, border=2)
return {
"statusCode": 200,
"headers": {"Content-Type": "image/png"},
"body": buf.getvalue(),
}
The one dependency is segno, a pure-Python QR library. No Pillow, no system libraries, so the build stays around five seconds instead of dragging in a C toolchain. You add it by typing segno into the Dependencies box. That's it. (Dependencies are a recent-ish addition; the first beta shipped with stdlib only.)
Paste the handler, pick Python 3.12, deploy. About a minute later you have:
https://fn-qr.orkestr.run/?data=https://orkestr.eu
Open that and a QR code fills the tab. Point your phone at it and it offers to open orkestr.eu. There's no step five.
That URL is public, which is what you want for a QR generator. It doesn't have to be. Every function has two auth modes: public, or API-key. Flip it to API-key and orkestr issues a key for you (they're shaped ork_fn_...). Callers pass it in an X-Orkestr-API-Key header, and anything without the right key gets a 401. You can rotate the key whenever you want. For the QR toy, public is fine. For a webhook receiver sitting on a signing secret, you'd gate it.
What this is good for (and what it isn't)
A QR endpoint is a toy, but the shape is not. The shape is: small input, small output, one job, public URL. That shape is everywhere once you start looking.
- A webhook receiver that validates a Mollie payment webhook and forwards the payload.
- An OG-image generator that takes
?title=and returns a PNG. - A short-link redirector backed by an env var or a single API call.
- A "contact form" endpoint for an otherwise static site.
- A badge generator (this whole blog's status badges are basically this function with different bytes).
None of these want a Next.js app around them. They want a function. Reaching for a full project with a Dockerfile and a deploy pipeline to host 30 lines is how you end up paying €70 a month and maintaining a YAML file for something that should be free and forgotten.
What it isn't good for: anything stateful, anything that wants a real database connection pool, anything compute-heavy, or a full web framework with dozens of routes. Those are apps. Deploy them as apps. The function is for the one thing.
Where it runs
On servers in Falkenstein, Germany. Same hardware as the rest of orkestr, same container hardening (cap-drop, no-new-privileges, non-root). Your handler is encrypted at rest. No US data transfer, which for a QR generator is irrelevant and for the Mollie-webhook version is the entire ballgame.
The container scales to zero after about a minute of no traffic, so an idle function costs nothing and holds no capacity. The first request after it sleeps pays a cold start - roughly 3 seconds on the free tier, faster on paid where you get more vCPU. I wrote about that tradeoff honestly in the beta post if you want the numbers.
Build it yourself
One function is free on the Starter plan - not a trial, just free. Paste the handler above, add segno, deploy. You'll have a working QR code API on your own subdomain before your coffee's cold.
And then, ideally, you forget about it. That's the highest compliment you can pay a tiny function. It just sits there at its URL, turning text into QR codes, asking nothing of you. The best infrastructure is the kind you stop thinking about.
Get the code
The full thing - handler, the one-line requirements.txt, and a README - lives at codeberg.org/orkestr/qr-code. Copy handler.py into a new function, paste segno into the Dependencies box, deploy. Nothing else to wire up.
Top comments (0)