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
BashThen, create a new project:
nest new my-serverless-api
cd my-serverless-api
BashThe 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 {}
JavaScriptThis 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();
}
}
JavaScriptRun 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
BashThis 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;
}
JavaScriptNow, 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;
}
}
JavaScriptNext, 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);
}
}
JavaScriptRestart 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
BashCreate 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);
}
JavaScriptThis 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
BashNext, update package.json
to include a build script that compiles the TypeScript code:
"scripts": {
"build": "nest build",
"start": "node dist/api/index.js"
}
JavaScriptSince 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"
}
]
}
JavaScriptThis 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
BashThis simulates Vercel’s serverless environment. Open http://localhost:3000/tasks
to verify the API works. If everything looks good, deploy to Vercel:
vercel
BashFollow 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!