Serverless scales traffic, not codebases
The platform will happily run a thousand concurrent requests. What it will not do is keep a hundred functions consistent. Past a certain size, the problems are organizational: duplicated vendor calls, inconsistent auth checks and no shared idea of where state lives.
Structure: thin handlers, shared modules
Each function should be a thin entry point: parse, authenticate, call shared logic, respond. The real work lives in shared modules that every handler imports.
- One module per external vendor
- One module for authentication and role checks, called first in every handler
- One module for HTTP responses and error shapes, so every endpoint fails the same way
- One module for rate limiting on public routes
State: pick the store for the failure you can tolerate
Relational data belongs in a managed database. But anything that must survive that database being unavailable (an operations ledger, an intake queue, a status board) is worth writing to a second, simpler store such as object or blob storage. It costs little and keeps the business running during an outage.
Idempotency everywhere
Webhooks are delivered at least once, and retries are normal. Every handler that writes should accept a key (the event ID, an order number) and do nothing on a repeat. This one rule removes most duplicate-record bugs.
Cold starts and cost
Cold starts matter for user-facing routes and rarely for background jobs. Keep user-facing bundles small, move heavy dependencies into background functions, and schedule batch work instead of running it on every request. Review cost per function monthly; a single chatty polling job is often most of the bill.
Testing at scale
Unit test the shared modules hard, including every vendor quirk you have found. Then keep a small set of end-to-end checks that call deployed endpoints after each release. A deploy that returns 200 on the homepage and 502 on every API route is a common, avoidable incident.