Connection Pooling with Vercel Functions | Vercel Knowledge Base

This guide shows best practices for connecting to relational databases with Vercel Functions. It includes improvements to address connection leak issues common in traditional serverless environments.

Use connection pooling

To get started with connecting to your relational database, we recommend using connection pooling with Fluid compute.

  1. Initialize a connection pool: Within the global scope of your function (or module), create a pool using your chosen database driver (e.g., pg for PostgreSQL or mysql2 for MySQL).
  2. Attach a pool: Use the attachDatabasePool helper to handle idle connections.
  3. Handle requests: Each incoming request can fetch a client from that already-initialized pool. This reuses the connection rather than opening a new one.
  4. Return the connection to the pool: After you’ve executed queries, release the connection back to the pool. In a concurrent environment, other requests can then grab that same connection.
import { Pool } from 'pg';

import { attachDatabasePool } from "@vercel/functions";

const pool = new Pool({

connectionString: process.env.DATABASE_URL,

});

// Attach the pool to ensure idle connections close before suspension

attachDatabasePool(pool);

// This function can now leverage a shared connection pool

export default async function handler(req, res) {

const client = await pool.connect();

try {
    const { rows } = await client.query('SELECT NOW()');
    res.status(200).json({ time: rows[0] });
  } finally {
    client.release();
  }
}

Best practices of using connection pooling

Why use connection pooling and Fluid compute?

Connecting to databases

Consider a traditional Node.js server connecting to a SQL database. When a request is made to your application, the server opens a connection to the database to execute a SQL query. After completion, the data is returned and the connection is closed.

At a large scale, creating a connection to your database on every request can exhaust server memory usage and hit the maximum number of connections allowed. One solution is to pay for more memory, and another is to use connection pooling.

What is connection pooling?

Pooling is one solution to prevent your application from exhausting all available database connections. Rather than opening a connection with every request, connection pooling allows us to designate a single "pooler" that keeps an active connection to the database. When a request is made that would read from the database, the pooler finds an available connection rather than creating a new connection.

Databases with Functions

On Fluid compute, the default execution model for new Vercel projects, Functions reuse global state within a warm instance. Multiple concurrent invocations share the same instance, so they can share a single database connection or connection pool instead of opening a new connection on every request. To pool connections safely, initialize your pool in the global scope and register it with attachDatabasePool from @vercel/functions, as the top of this guide shows. This helper closes idle connections before an instance suspends, which keeps your connection count under control.

Traditional serverless background

Before Fluid compute, Functions ran in stateless, short-lived environments that were not designed for persistent connections to a database. Traditional relational databases were built for long-running compute instances, not the ephemeral nature of those functions. Because functions scaled immediately during traffic spikes, they could exhaust available database connections quickly. When a function was invoked, it opened a connection to the database, and it closed that connection on completion. Maximizing connection reuse in that model required different solutions than standard connection pooling.

Open source solutions like serverless-mysql and serverless-pg attempt to bring connection pooling to those serverless environments. By storing variables outside the scope of the function, these solutions can create a connection pool in between invocations. However, there is no guarantee of connection reuse, so we recommend other solutions for projects not using Fluid compute.

HTTP Database APIs

Services like Supabase (which uses PostgREST), Hasura, or AWS Aurora Data API expose a managed API layer on top of the underlying database. This allows you to execute SQL statements from any application over HTTP without using any drivers or plugins.

These Data APIs don’t require a persistent connection to the database. Instead, they provide an HTTP endpoint where you can run your SQL statements without managing connections.

Connection leaks problem

When functions are used with connection pools, you can face the following problem when a function is suspended:

Connection pooling with Fluid Compute

By default, functions run in short-lived, isolated environments. This makes it difficult to maintain persistent database connections—especially under heavy load. Fluid Compute changes this by enabling multiple invocations to share a single function instance (and its global state). As a result, you can maintain and reuse database connections without having to open and close them on every request.

Reduced connection overhead: Traditional serverless environments scale by creating new function instances for each burst of traffic. Each instance may open its own connection to the database, often leading to connection limits being hit. With Fluid Compute, when multiple concurrent requests are routed to the same instance, they can reuse the same database connection or connection pool, dramatically cutting down on connection overhead.

Optimized concurrency: Fluid Compute's optimized concurrency allows several requests to run simultaneously within the same instance. This setup closely resembles a traditional server environment that maintains a global connection pool. The difference is that the platform still automatically scales up additional instances as needed—giving you the best of both worlds.

Persistent Global State: Because Fluid Compute does not fully tear down instances between invocations, objects or pools you initialize at a global level remain available for subsequent requests. This allows for strategies such as lazy initialization of your database pool: the first request establishes the pool, and subsequent requests reuse it until the platform decides to spin down that instance due to inactivity or scaling needs.

Solution for the connection leak problem: It provides a lifecycle hook attachDatabasePool that ensures idle connections are closed before suspension. When you attach a pool to your function using this helper, Fluid Compute uses waitUntil to keep the instance alive just long enough to close idle connections after a request completes. This makes connection lifecycle behavior identical to a serverful environment and prevents leaks.