After spending hours getting my NestJS app up and running on Vercel, I figured it was time to document what I learned—not only to save myself time in the future, but hopefully to help others avoid some of the pitfalls I ran into. Here’s a breakdown of what worked, what didn’t, and how I finally got everything running smoothly.
This might also be interesting for you: Adding Swagger to Node Server
Step 1: Setting Up NestJS Vercel hosting
First things first, getting the basic setup to deploy on Vercel. Vercel is awesome for serverless, but working with NestJS needed a few tweaks. The main thing is to set up a vercel.json
configuration file, which tells Vercel exactly how to handle your app.
Here’s the configuration I ended up with:
{ "version": 2, "builds": [ { "src": "src/main.ts", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "src/main.ts", "methods": [ "GET", "POST", "PUT", "PATCH", "OPTIONS", "DELETE", "HEAD", "CONNECT", "TRACE" ] } ] }
I deployed it to Vercel and got the following error:
This Serverless Function has crashed. Your connection is working correctly. Vercel is working correctly. 500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED ID: bom1::sgk4v-1711014022883-1e9ed54f4c37
Looking in the logs, I noticed the database connection was an issue and in addition got the following log message:
No exports found in module "/var/task/app-name/src/main.js". Did you forget to export a function or a server?
Turned out I could ignore the second part of the error message and just focus on the database connection.
Step 2: Configuring the Database
For my app, I used a mysql database with multiple schemas. I tried several free offers, but they were not compatible with the multiple schemas approach. Therefore I ended up with hosting it on Google Cloud. I scaled it down to a price of 0.01$ per hour and used the 300$ newbie offer.
Allowing Vercel to connect required setting the IP to 0.0.0.0/0
in Google Cloud’s configuration, making the database accessible from any IP address. Important note: make sure you test locally before deploying to Vercel, or you’ll be dealing with errors like these:
Step 3: Dealing with CORS
CORS caused also some headaches. Make sure you allow OPTIONS
for CORS preflight requests, as Vercel needed explicit permission for cross-origin requests. I ended up adding a lot of headers to make sure requests were allowed:
app.enableCors({ origin: 'domain-name', credentials: true, methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', allowedHeaders: [ 'Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization', ], });
Step 4: Switching to express-session
and Redis for Session Management
One of the trickiest parts was getting sessions to work. I started with the cookie-session
library, but Vercel completely ignore it. After digging into the docs and some trial and error, I switched to express-session
, which is more popular and works nicer with Vercel’s serverless environment.
For some reason the import syntax has to be exactly like this:
import session = require('express-session');
I also had to configure the session middleware with trust proxy
enabled, since Vercel proxies requests. Here’s what the final setup looked like:
const expressApp = app.getHttpAdapter().getInstance(); expressApp.set('trust proxy', true);
Also setting secure: true
and sameSite: 'none'
was essential to ensure cookies work across HTTPS and cross-origin requests!
Keep in mind, with Vercel, multiple serverless instances can handle requests simultaneously, which caused session conflicts. To fix this, I connected my session storage to a Redis instance. Luckily this was super easy.
Redis keeps session data consistent, avoiding conflicts across requests, especially under load. The code I ended up with:
const expressApp = app.getHttpAdapter().getInstance(); expressApp.set('trust proxy', true); const redisClient = createClient({ password: process.env.REDIS_PASSWORD, socket: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT, 10), }, }); redisClient .connect() .catch((err) => console.log('Could not establish a connection with Redis: ' + err), ); redisClient.on('error', (err) => console.log('Redis error: ' + err)); redisClient.on('connect', () => console.log('Connected to Redis successfully'), ); app.use( session({ store: new RedisStore({ client: redisClient }), secret: process.env.COOKIE_SECRET, cookie: { secure: process.env.NODE_ENV !== 'development', sameSite: process.env.NODE_ENV === 'development' ? 'lax' : 'none', maxAge: 86400000, }, }), );
Step 5: Add withCredentials
in the Frontend
This step is just a side note: For session cookies to work between the frontend and backend, withCredentials
need to be set to true
on my frontend’s HTTP requests. This allows cookies to be included in cross-origin requests, which is important when the frontend and backend are hosted separately. I had to make sure Angular’s HTTP client had this setting enabled.
Final Thoughts to NestJS Vercel hosting
Deploying my NestJS app on Vercel was a true roller coaster. Sometimes, I felt like I was on the verge of getting everything working perfectly, only to be hit with new errors that sent me back to troubleshooting mode. There were moments of frustration—especially around the session handling and CORS issues. But each solution brought a new high, and every error fixed felt like a little victory.
Now, with everything finally working smoothly, I can say it feels awesome. Seeing my app live and functioning the way I envisioned is worth all the headaches. It’s a huge relief, but even more, it’s deeply satisfying to know I’ve overcome each hurdle and can look back on what I learned. I hope this guide can save others some of those low points and help them reach that “it just works” moment a little faster!