How to deploy Node.js app to Vercel

Deploying Node.js applications to Vercel provides serverless function deployment with automatic builds, edge network, and Git integration. With over 12 years of Node.js experience since 2014 and as the creator of CoreUI, I’ve deployed numerous Node.js APIs to Vercel. Vercel excels at serverless Node.js functions with automatic scaling, zero configuration deployments, and global edge network for low latency. This approach works perfectly for API routes, webhooks, and serverless backends with instant deployments on every push.

Use Vercel CLI or Git integration to deploy Node.js serverless functions with automatic HTTPS and environment configuration.

Install Vercel CLI:

# Install globally
npm install -g vercel

# Login
vercel login

# Deploy current directory
vercel

# Deploy to production
vercel --prod

Basic serverless function:

// api/hello.js
export default function handler(req, res) {
  res.status(200).json({
    message: 'Hello from Vercel!',
    timestamp: new Date().toISOString()
  })
}

API with Express-like routing:

// api/users.js
export default async function handler(req, res) {
  const { method, query } = req

  switch (method) {
    case 'GET':
      // Get user by ID
      const userId = query.id
      return res.status(200).json({
        id: userId,
        name: 'John Doe',
        email: '[email protected]'
      })

    case 'POST':
      // Create user
      const body = req.body
      return res.status(201).json({
        success: true,
        user: body
      })

    default:
      res.setHeader('Allow', ['GET', 'POST'])
      return res.status(405).end(`Method ${method} Not Allowed`)
  }
}

Environment variables:

// api/config.js
export default function handler(req, res) {
  // Access environment variables
  const dbUrl = process.env.DATABASE_URL
  const apiKey = process.env.API_KEY

  res.status(200).json({
    environment: process.env.VERCEL_ENV, // production, preview, or development
    region: process.env.VERCEL_REGION
  })
}

// Set via Vercel CLI
// vercel env add DATABASE_URL

// Or in vercel.json

vercel.json configuration:

{
  "version": 2,
  "builds": [
    {
      "src": "api/**/*.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/api/users/(.*)",
      "dest": "/api/users.js"
    },
    {
      "src": "/api/(.*)",
      "dest": "/api/$1.js"
    }
  ],
  "env": {
    "NODE_ENV": "production"
  },
  "regions": ["iad1"]
}

Database integration:

// api/db/users.js
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false
  }
})

export default async function handler(req, res) {
  try {
    const { rows } = await pool.query('SELECT * FROM users LIMIT 10')

    res.status(200).json({
      users: rows,
      count: rows.length
    })
  } catch (error) {
    console.error('Database error:', error)
    res.status(500).json({
      error: 'Database query failed'
    })
  }
}

Middleware pattern:

// lib/middleware.js
export function withAuth(handler) {
  return async (req, res) => {
    const token = req.headers.authorization?.replace('Bearer ', '')

    if (!token) {
      return res.status(401).json({ error: 'Unauthorized' })
    }

    try {
      // Verify token
      const user = verifyToken(token)
      req.user = user
      return handler(req, res)
    } catch (error) {
      return res.status(401).json({ error: 'Invalid token' })
    }
  }
}

export function withCors(handler) {
  return async (req, res) => {
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')

    if (req.method === 'OPTIONS') {
      return res.status(200).end()
    }

    return handler(req, res)
  }
}

// api/protected.js
import { withAuth } from '../lib/middleware'

async function handler(req, res) {
  res.status(200).json({
    message: 'Protected data',
    user: req.user
  })
}

export default withAuth(handler)

Edge functions:

// api/edge-hello.js
export const config = {
  runtime: 'edge'
}

export default function handler(req) {
  return new Response(
    JSON.stringify({
      message: 'Hello from Edge!',
      location: req.geo.city
    }),
    {
      status: 200,
      headers: {
        'content-type': 'application/json'
      }
    }
  )
}

Caching:

// api/cached.js
export default function handler(req, res) {
  // Cache for 60 seconds
  res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate')

  res.status(200).json({
    data: 'This response is cached',
    timestamp: new Date().toISOString()
  })
}

GitHub integration:

# Connect repository via Vercel Dashboard
# or use CLI
vercel link

# Automatic deployments on:
# - Push to main branch (production)
# - Pull requests (preview deployments)

# View deployments
vercel ls

# Alias deployment to custom domain
vercel alias set deployment-url.vercel.app custom-domain.com

Package.json configuration:

{
  "name": "nodejs-vercel-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "vercel dev",
    "deploy": "vercel --prod"
  },
  "dependencies": {
    "pg": "^8.11.3"
  },
  "devDependencies": {
    "vercel": "^33.0.1"
  }
}

Best Practice Note

Use serverless functions in api/ directory for automatic routing. Keep functions lightweight and stateless. Use environment variables for secrets. Enable caching with Cache-Control headers. Use Edge Functions for low-latency global endpoints. Connect GitHub for automatic preview deployments on pull requests. Use vercel dev for local development matching production environment. Implement middleware for auth and CORS. This is how we deploy CoreUI Node.js APIs to Vercel—serverless functions with automatic scaling, global edge network, and instant deployments with zero configuration.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author