Serverless functions have a surprisingly high ratio of "looked simple, turns out complex" scenarios. The execution model is clean in theory — write a function, deploy, it runs on request — but the operational reality has sharp edges that aren't obvious until you've hit them.
Here are five I've seen repeatedly.
Mistake 1: Putting secrets in environment variables without encryption
Environment variables are visible in deployment logs and dashboards. Use a secrets manager (AWS Secrets Manager, Doppler, HashiCorp Vault) for anything sensitive. Retrieve secrets at runtime, not at deploy time via plain env vars.
Mistake 2: Creating database connections inside the handler
// Bad: new connection on every invocation
export const handler = async () => {
const db = new Pool({ connectionString: process.env.DB_URL })
// ...
}
// Good: connection outside handler, reused across warm invocations
const db = new Pool({ connectionString: process.env.DB_URL })
export const handler = async () => {
// ...
}
Cold start creates a new connection. Warm invocations reuse the existing one. Putting the connection inside the handler means a new connection per request — expensive and often hitting connection pool limits.
Mistake 3: Not handling idempotency
Functions are invoked at-least-once in most serverless platforms. Handle duplicate invocations by checking an idempotency key before doing work. A simple database row with the event ID is sufficient.
Mistake 4: Ignoring the cold start budget
Your function's deploy package size directly affects cold start time. Audit your dependencies. moment.js is famously large — replace it with date-fns or the native Intl API. Avoid importing entire AWS SDK when you only need one service client.
Mistake 5: No local development story
Running serverless functions locally shouldn't require deploying to a cloud environment to test. Set up a local development workflow — for moqapi.dev functions there's an in-browser editor; for AWS Lambda, SAM or LocalStack. Test locally before every deploy.
Top comments (0)