NestJs Vercel Hosting

Lessons Learned: Hosting NestJS App on Vercel

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.

Step 6: Include font

To include font files in your NestJS project, you can use the compilerOptions in the nest-cli.json file to define assets for build output, like specifying "include": "**/*.ttf" and "outDir": "dist/src".

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": [
      {
        "include": "**/*.ttf",
        "outDir": "dist/src"
      }
    ],
    "deleteOutDir": true
  }
}

After the build, ensure the fonts are correctly referenced in your code using path.resolve, e.g., path.resolve(__dirname, '../fonts/Roboto-Regular.ttf'). This approach ensures the font files are bundled with the build and accessible during runtime.

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!

Leave a Reply

Your email address will not be published. Required fields are marked *


Cookie Consent with Real Cookie Banner