A cartoon-style thumbnail featuring a cheerful rocket launching from a colorful cityscape of code blocks and TypeScript logos. A developer holds a glowing NestJS logo, surrounded by playful Vercel icons like clouds and lightning bolts, all in a vibrant rainbow palette with sparkles and motion lines.

Building Serverless APIs with NestJS & Vercel Functions

by Evgenii Studitskikh
8 minutes read

Serverless architecture has changed how we build and deploy applications. It lets developers focus on writing code without worrying about managing servers, scaling infrastructure, or handling downtime. For those looking to create robust APIs, combining NestJS—a progressive Node.js framework—with Vercel Functions offers a powerful, scalable, and developer-friendly solution. In this article, I’ll walk you through the process of building serverless APIs using these tools, keeping things approachable yet professional. Whether you’re new to serverless or a seasoned developer, my goal is to make this clear and actionable for your next project.

Why NestJS and Vercel Functions?

NestJS is a framework that brings structure to Node.js development. It’s built with TypeScript, follows a modular architecture, and draws inspiration from Angular’s dependency injection and decorators. This makes it a great choice for creating maintainable APIs with clean code. Its out-of-the-box support for features like dependency injection, middleware, and exception handling saves time and reduces boilerplate.

Vercel Functions, on the other hand, provide a seamless serverless environment. They’re part of Vercel’s platform, which is known for hosting Next.js applications but works beautifully for APIs too. Vercel Functions scale automatically, handle HTTP requests efficiently, and integrate with Git for effortless deployments. Pairing NestJS with Vercel Functions means you get a structured backend with the flexibility of serverless deployment—a combo that’s hard to beat.

Setting Up Your NestJS Project

Let’s start by creating a new NestJS project. Assuming you have Node.js installed, open your terminal and install the NestJS CLI globally:

npm install -g @nestjs/cli
Bash

Then, create a new project:

nest new my-serverless-api
cd my-serverless-api
Bash

The CLI generates a project structure with a main module, a sample controller, and a service. For now, let’s keep it simple and focus on building an API to manage a list of tasks. Open src/app.module.ts and ensure it looks something like this:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
JavaScript

This is the root module where we’ll plug in our task-related functionality later. The CLI also sets up a basic controller in src/app.controller.ts. Let’s modify it to handle a simple endpoint:

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
JavaScript

Run the app with npm run start:dev and visit http://localhost:3000. You should see the default “Hello World!” response. This confirms your NestJS setup is working.

Creating a Task Module

To make our API functional, let’s create a module for managing tasks. Generate a new module, controller, and service using the NestJS CLI:

nest generate module tasks
nest generate controller tasks
nest generate service tasks
Bash

This creates a tasks folder under src with the necessary files. Let’s define a simple task interface in src/tasks/task.interface.ts:

export interface Task {
  id: number;
  title: string;
  completed: boolean;
}
JavaScript

Now, update the TasksService in src/tasks/tasks.service.ts to manage tasks in memory (for simplicity; in a real app, you’d use a database):

import { Injectable } from '@nestjs/common';
import { Task } from './task.interface';

@Injectable()
export class TasksService {
  private tasks: Task[] = [
    { id: 1, title: 'Learn NestJS', completed: false },
    { id: 2, title: 'Deploy to Vercel', completed: false },
  ];

  findAll(): Task[] {
    return this.tasks;
  }

  findOne(id: number): Task {
    return this.tasks.find(task => task.id === id);
  }

  create(task: Partial<Task>): Task {
    const newTask = {
      id: this.tasks.length + 1,
      title: task.title,
      completed: false,
    };
    this.tasks.push(newTask);
    return newTask;
  }
}
JavaScript

Next, wire up the TasksController in src/tasks/tasks.controller.ts to expose endpoints:

import { Controller, Get, Post, Body, Param, ParseIntPipe } from '@nestjs/common';
import { TasksService } from './tasks.service';
import { Task } from './task.interface';

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  @Get()
  findAll(): Task[] {
    return this.tasksService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number): Task {
    return this.tasksService.findOne(id);
  }

  @Post()
  create(@Body() task: Partial<Task>): Task {
    return this.tasksService.create(task);
  }
}
JavaScript

Restart the app and test the endpoints using a tool like Postman or curl. For example:

  • GET http://localhost:3000/tasks returns the task list.
  • GET http://localhost:3000/tasks/1 fetches a single task.
  • POST http://localhost:3000/tasks with {"title": "New Task"} creates a new task.

This gives us a working API, but it’s still running locally. Let’s make it serverless with Vercel.

Preparing for Vercel Functions

Vercel Functions expect a specific file structure for serverless APIs. Each endpoint lives in the api directory, and Vercel maps requests to these files. NestJS, by default, runs as a single server, so we need to adapt it for Vercel’s serverless model.

First, install the Vercel CLI:

npm install -g vercel
Bash

Create an api directory at the project root and add an index.ts file to act as the entry point for Vercel Functions. This file will bootstrap the NestJS app and handle incoming requests. Here’s how it looks:

import { NestFactory } from '@nestjs/core';
import { AppModule } from '../src/app.module';
import type { VercelRequest, VercelResponse } from '@vercel/node';

export default async function handler(req: VercelRequest, res: VercelResponse) {
  const app = await NestFactory.create(AppModule);
  await app.init();

  const handler = app.getHttpAdapter().getInstance();
  handler(req, res);
}
JavaScript

This code creates a NestJS app instance and passes Vercel’s request and response objects to the NestJS HTTP adapter. To make TypeScript happy, install the Vercel Node types:

npm install --save-dev @vercel/node
Bash

Next, update package.json to include a build script that compiles the TypeScript code:

"scripts": {
  "build": "nest build",
  "start": "node dist/api/index.js"
}
JavaScript

Since Vercel Functions are serverless, we need to tell Vercel how to build and run our app. Create a vercel.json file at the root:

{
  "version": 2,
  "builds": [
    {
      "src": "api/index.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "api/index.ts"
    }
  ]
}
JavaScript

This configuration tells Vercel to treat api/index.ts as a Node.js function and route all requests to it. The @vercel/node builder handles TypeScript compilation automatically.

Deploying to Vercel

Before deploying, test locally with the Vercel CLI:

vercel dev
Bash

This simulates Vercel’s serverless environment. Open http://localhost:3000/tasks to verify the API works. If everything looks good, deploy to Vercel:

vercel
Bash

Follow the prompts to link your project to a Vercel account and choose a Git repository if desired. Once deployed, Vercel provides a URL (e.g., https://my-serverless-api.vercel.app). Test the endpoints:

  • GET https://my-serverless-api.vercel.app/tasks
  • POST https://my-serverless-api.vercel.app/tasks

Your API is now live, running serverlessly with zero server management. Vercel handles scaling, so whether you have ten users or ten thousand, the platform adjusts automatically.

Optimizing and Scaling

One thing to keep in mind is that serverless functions are stateless. If you used an in-memory task list like in our example, data resets between function invocations. For a production app, connect to a database like MongoDB Atlas or Vercel’s own Postgres offering. NestJS makes this easy with modules like @nestjs/mongoose or @nestjs/typeorm.

Another tip: Vercel Functions have limits on execution time and payload size. Keep your endpoints lightweight and avoid heavy computations. If you’re handling large datasets or complex logic, consider offloading tasks to background jobs or integrating with a message queue.

For more on optimizing performance in modern frameworks, check out my article on optimizing performance in Next.js 14, which dives into techniques that complement serverless APIs.

Learning More

If you’re curious to explore further, the NestJS documentation is a fantastic resource for diving deeper into modules, providers, and middleware. For Vercel-specific tips, their serverless functions guide covers advanced configurations and best practices.

Building serverless APIs with NestJS and Vercel Functions is a game-changer. You get the structure and type safety of NestJS, paired with the scalability and simplicity of Vercel’s platform. Whether you’re prototyping a side project or deploying a production-grade API, this stack empowers you to move fast without sacrificing quality. Try it out, experiment, and let me know how it goes on my blog!

You may also like