Vercel can host a Node.js API, including Express, when the application fits a request-driven serverless model. It is a good match for portfolio APIs, webhooks, and small backends. It is a poor match for a worker that must run forever or an application that depends on local disk state.
Before you deploy: check the fit
| Workload | Vercel fit | Reason |
|---|---|---|
| REST or JSON API | Good | Each request can run independently |
| Express portfolio backend | Good | Minimal setup and automatic deployments |
| Webhook receiver | Good | Short request, validation, durable write |
| Background queue worker | Poor | Workers need a continuously running process |
| Local file uploads | Poor | Function filesystems are not durable storage |
| Stateful WebSocket rooms | Poor | Presence and pub/sub need external state |
If your API writes to PostgreSQL, Redis, object storage, or another managed service, that is normal. The function should be replaceable; durable state should live elsewhere.
Create the Express project
mkdir vercel-express-api
cd vercel-express-api
npm init -y
npm install express
Create api/index.js:
const express = require('express');
const app = express();
app.use(express.json());
app.get('/health', (_request, response) => {
response.json({ ok: true });
});
app.post('/api/messages', async (request, response) => {
const message = String(request.body?.message ?? '').trim();
if (!message) {
return response.status(400).json({ error: 'message is required' });
}
return response.status(201).json({ message });
});
module.exports = app;
Vercel’s Express guide uses an /api entry point. Keep the first deployment small so routing problems are easy to isolate.
Route requests to the API
If you want every path to reach Express, add vercel.json:
{
"version": 2,
"rewrites": [
{ "source": "/(.*)", "destination": "/api" }
]
}
If the repository also contains a frontend, do not rewrite every path blindly. Route only the API prefix and let the frontend framework own page routes.
Test the Vercel-shaped app locally
Running node api/index.js does not reproduce Vercel’s routing and function environment. Use the CLI:
npx vercel login
npx vercel dev
Then test both success and failure:
curl http://localhost:3000/health
curl -i -X POST http://localhost:3000/api/messages \
-H 'content-type: application/json' \
-d '{}'
The second request should return 400. A deployment check that only opens the homepage misses validation and error-handling problems.
Add environment variables safely
Use project settings or the Vercel CLI for secrets such as DATABASE_URL, API keys, and JWT secrets. Keep local values in .env.local and exclude them from Git.
.env
.env.local
.vercel
node_modules
Remember that Preview and Production are separate environments. A preview deployment can work while production fails because a variable exists in only one environment.
Validate required configuration at startup instead of discovering it inside the first user request:
const required = ['DATABASE_URL', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing environment variable: ${key}`);
}
}
Deploy a preview, then production
npx vercel
That creates a preview URL. Test /health, one database-backed route, one invalid request, and CORS from the real frontend origin.
When the preview passes:
npx vercel --prod
Connecting a Git repository gives you the same useful workflow automatically: branches and pull requests get previews, while the production branch creates production deployments.
Database connection mistakes
Serverless traffic can create several function instances, and each instance may open database connections. Reusing a client inside the module and using a serverless-friendly pool or provider helps prevent connection exhaustion.
Do not cache user-specific data in a module variable and assume it belongs to one user. A warm function instance can serve later requests.
Why an app works locally but fails on Vercel
| Symptom | Likely cause | Check |
|---|---|---|
404 on every route |
Entry file or rewrite mismatch | Deployment output and vercel.json |
| Database timeout | Network allowlist or too many connections | Runtime logs and pool limits |
| CORS error | Production frontend origin is missing | Response headers and environment variables |
| Secret is undefined | Variable added to the wrong environment | Preview vs Production settings |
| Uploaded file disappears | Local function storage was used | Move files to object storage |
| Request times out | Long task runs inside the request | Move work to a queue or job system |
Read the runtime logs for the exact production request. Repeated redeploys do not fix a routing or configuration mismatch.
When to choose another host
Choose a container, VPS, or worker platform when you need a process that stays alive, custom operating-system packages, long background tasks, or direct control over networking. Vercel can still host the frontend while the backend worker runs elsewhere.
The practical rule is simple: use Vercel when requests are independent and external services hold durable state. Choose a continuously running host when the process itself is part of the application’s state.
Discussion
What would you try, change, or challenge after reading this guide? Specific results and errors help the next reader.
Comments will load as you reach this section.