Serverless Functions And API Routes
A frontend app often needs a small amount of backend behavior. It may need to send a contact form, receive a webhook, create a checkout session, verify a signed request, read from a database, or call a private API. Vercel Functions let you add that server-side code without managing your own server process, operating system, reverse proxy, or deployment script.
This does not mean Functions are magic, free, or unlimited. A Function is still production code. It receives untrusted input, uses CPU and memory, may call external services, can fail at runtime, and can create cost. The value is that Vercel handles the deployment and scaling mechanics while you keep the function small, explicit, observable, and secure.
What A Vercel Function Is
A Vercel Function is server-side code that runs when a matching request reaches your deployment. Vercel creates invocations for incoming requests, can reuse instances for efficiency, and scales down when there are no incoming requests. You write the handler; Vercel provides the managed runtime around it.
In a framework-less project, a simple function can live under api/:
export default {
fetch(request: Request) {
return new Response('Hello from Vercel!');
}
};
In a Next.js App Router project, an API endpoint commonly uses a route.ts file:
export function GET() {
return Response.json({ ok: true });
}
Both examples are intentionally small. Real functions should validate input, handle errors, use Environment Variables for secrets, and return useful status codes.
When Functions Are A Good Fit
Vercel Functions are well suited for small backend endpoints that are close to a web app. Good beginner examples include contact forms, newsletter signup handlers, webhook receivers, private API wrappers, simple database reads, authenticated profile endpoints, and checkout-session creation.
They are especially useful when the browser must not hold a secret. The frontend calls your function. The function uses the secret server-side. The browser receives only the result it is allowed to see.
A Function is usually a good fit when:
The endpoint is request/response shaped.
The work is tied to the web app.
The code can finish within platform limits.
The function can be retried or handled safely if it fails.
The secret must stay server-side.
Logs are enough to debug normal problems.
A Function may not be the right fit for long-running background workers, heavy video processing, persistent socket servers, queue consumers that must run forever, or workloads that need direct control over the host. Those jobs may need a dedicated worker platform, queue, container, or managed service.
Contact Forms
A contact form is a classic Function use case. The frontend collects a name, email, and message. The function validates the input, checks basic abuse controls, calls an email provider, and returns success or failure.
A beginner-safe shape looks like this:
Browser form -> /api/contact -> email provider
The email provider API key stays in Vercel Environment Variables. The browser never receives it. The function should reject missing fields, oversized messages, invalid email formats, and unsupported methods. It should return 400 for invalid input, 405 for unsupported methods, and 500 only for unexpected server failures.
Do not log the full message body in production. Contact forms can contain private customer information. Log enough to debug the request path and provider status, but avoid storing sensitive message content in logs unless you have a clear policy for it.
Webhooks
A webhook is an HTTP request sent by another service to your app. Payment providers, Git platforms, email services, CMS tools, and monitoring systems commonly use webhooks. Vercel Functions can receive those requests, verify them, and perform a small action.
Webhook handlers need stricter habits than simple demo endpoints. Verify the provider signature when the provider supports signing. Reject unexpected methods. Keep the raw body when signature verification requires it. Return quickly when the provider expects a fast response. Make repeated deliveries safe, because many providers retry failed webhooks.
A safe webhook checklist is:
Verify the signature or shared secret.
Reject unsupported HTTP methods.
Validate the event type.
Make processing idempotent.
Avoid logging secrets or full payloads.
Return the correct status code.
Monitor Runtime Logs after deployment.
Idempotency matters. If a payment webhook arrives twice, your app should not grant the same paid access twice or send duplicate customer emails.
Private API Wrappers
Many frontend apps need data from a private service. Calling that service directly from browser JavaScript would expose the secret token. A Function can act as a narrow middle layer.
The browser sends a limited request:
GET /api/public-product-summary?id=123
The Function validates the id, calls the private API with a server-side token, filters the response, and returns only the safe fields.
This pattern is not just about hiding the token. It also lets you control input validation, rate limiting, error messages, response shape, and provider changes. If the third-party API changes, you can update the function without rewriting every frontend component.
Runtime Logs
Build Logs tell you what happened while the deployment was being built. Runtime Logs tell you what happened after a user or service called your deployed function. When a contact form fails only on the live URL, Runtime Logs are where you look.
Use logs intentionally. Good logs explain the request path and failure class without exposing secrets:
console.info('contact request received');
console.warn('contact validation failed', { reason: 'missing-email' });
console.error('email provider failed', { providerStatus: 503 });
Bad logs dump API keys, authorization headers, cookies, entire webhook payloads, database URLs, or personal information. Logs are operational data and may be visible to teammates or exported to another logging system on paid plans.
When debugging, filter logs by deployment, path, status, and time window. Reproduce the issue in Preview first when possible, then compare Production only if the bug is production-specific.
Duration And Cold-Start Awareness
Serverless and managed functions are designed for request-sized work. They can start from zero when traffic returns after quiet periods, and that can add latency. Vercel also measures function usage with inputs such as invocations, memory, CPU, and active execution time. Long or inefficient functions can affect cost and user experience.
Design beginner functions to do one small job. Validate input before calling external services. Avoid unnecessary sequential API calls. Set timeouts for slow providers when your library supports it. Return clear errors instead of leaving the browser waiting indefinitely.
Cold starts are not usually the first thing to optimize in a learning project, but they are worth understanding. If the first request after a quiet period is slower than the next request, that may be startup overhead. Keep dependencies reasonable, avoid loading huge libraries for tiny tasks, and move expensive background work out of request handlers when it grows beyond the endpoint's purpose.
Status Codes And Error Shapes
A useful API endpoint communicates what happened. Returning 200 for every case forces the frontend to guess. Returning raw provider errors can expose details you do not want users to see.
A simple convention is:
200 or 201: request succeeded
400: invalid user input
401 or 403: authentication or authorization problem
404: requested resource not found
405: method not allowed
409: conflict, duplicate, or state mismatch
429: too many requests
500: unexpected server problem
502 or 503: upstream provider problem
Pair status codes with small response bodies. For example:
{
"ok": false,
"error": "Email is required."
}
Keep internal details in logs. Give users messages they can act on.
Security Basics
Every public Function is an internet-facing endpoint. Do not trust the browser just because you wrote the frontend. Users can call the endpoint directly with curl, scripts, browser DevTools, or automated tools.
At minimum, validate input, reject unexpected methods, require authentication where needed, keep secrets in Environment Variables, use CSRF or same-site protections where appropriate for cookie-based flows, and avoid returning private records without authorization checks.
For webhooks, authenticate the sender. For contact forms, consider spam controls. For payment actions, calculate prices on the server or fetch product prices from a trusted source. Do not let the browser tell the server what a user should pay.
Local And Preview Testing
Test Functions in three places: locally, in Preview, and in Production after release. Local testing catches syntax and logic mistakes. Preview catches deployment-environment issues such as missing Environment Variables or provider callbacks pointed at the wrong URL. Production smoke tests confirm the real domain and real environment are wired correctly.
For a contact form, test valid input, missing email, an oversized message, a provider failure if you can simulate one, and repeated submissions. For a webhook, test valid signatures, invalid signatures, duplicate events, unknown event types, and provider retries.
Do not promote a function after testing only the happy path. Backend endpoints fail most often in the edges: malformed input, missing secrets, timeouts, duplicate events, and provider outages.
Ownership And Retries
For production work, decide who owns each Function and what should happen when it fails. A contact endpoint may show the user an error and ask them to try again. A webhook endpoint may receive the same event later from the provider. A private API wrapper may need a frontend retry, a cached fallback, or a clearer unavailable state. Writing that expectation down keeps small endpoints from becoming mysterious production dependencies.
Common Mistakes
The first mistake is using a Function as a dumping ground for all backend work. Keep endpoints focused.
The second mistake is trusting frontend validation. Browser validation improves user experience, but server validation protects the system.
The third mistake is exposing secrets through public environment variable prefixes or response bodies.
The fourth mistake is logging sensitive payloads.
The fifth mistake is ignoring duration and cost. A function that waits on slow providers for every request can become both expensive and frustrating.
The sixth mistake is assuming a successful build means the endpoint works. Functions can fail only at runtime.
Review Questions
- What kind of work is a Vercel Function good at handling?
- Why should a contact form use a server-side endpoint instead of an email API key in the browser?
- What makes webhook handlers different from ordinary form handlers?
- Why are Runtime Logs different from Build Logs?
- What should you avoid logging from a function?
- Why should backend endpoints validate input even when the frontend already validates it?