Skip to content
Docs

Connect Next.js to Amazon Aurora PostgreSQL using Vercel Marketplace

Learn how to connect your Next.js application to Amazon Aurora PostgreSQL securely using the Vercel Marketplace AWS integration

Michael Toth, Ismael Rumzan

Securing database connections is a critical part of building production applications. Traditional methods often involve managing long-lived database credentials, which increases the risk of credential leaks. Vercel's AWS integration simplifies this by using OIDC Federation and RDS IAM authentication, allowing your Next.js application to connect to Aurora PostgreSQL without hardcoded access tokens or passwords.

This guide shows you how to set up a secure connection, implement database queries, and fetch data in a Next.js Server Component.

Copy link to headingOverview

By the end of this guide, you will be able to:

  • Connect a Next.js application to AWS Aurora PostgreSQL securely
  • Use Vercel OIDC federation to authenticate with AWS
  • Implement RDS IAM authentication for database access without passwords
  • Fetch and display data from your database using Server Components

Copy link to headingHow it works

Understanding the security flow helps you manage your database connections effectively.

Copy link to headingOIDC federation and RDS IAM authentication

Instead of using a static username and password, this approach uses short-lived tokens:

  1. OIDC federation: Vercel provides an OIDC token to your serverless functions. This token identifies the Vercel project and environment.
  2. AWS role assumption: Your AWS IAM role is configured to trust Vercel's OIDC provider. When your function runs, it exchanges the Vercel OIDC token for temporary AWS credentials.
  3. RDS IAM authentication: Using these temporary credentials, your application generates an auth token specifically for Aurora PostgreSQL. This token acts as a temporary password to connect to the database.

This multi-layered approach ensures that no long-lived secrets are stored in your environment variables, significantly reducing your security surface area.

Copy link to headingPrerequisites

Before you begin, make sure you have:

  • A Vercel account
  • Vercel CLI installed (npm i -g vercel)
  • Node.js 18 or later installed locally

Copy link to heading1. Set up the Vercel AWS integration

Before connecting your application, you need a running Aurora PostgreSQL cluster.

  1. Go to the Vercel Marketplace AWS integration and select install for the Amazon Aurora PostgreSQL product
  2. Follow the setup wizard to provision a new Aurora PostgreSQL database
  3. The integration automatically configures IAM authentication and creates the necessary roles

The Vercel Marketplace handles the complex AWS setup, including security groups, IAM roles, and the OIDC trust relationship.

Copy link to heading2. Connect your project

To develop locally with the same secure connection, link your project to Vercel and pull the environment variables.

terminal
vercel link

Select your team and the project connected to the AWS integration.

Copy link to headingPull environment variables

terminal
vercel env pull

This creates a .env.local file with the required variables:

  • AWS_ROLE_ARN - The IAM role your application assumes
  • AWS_REGION - The AWS region of your Aurora cluster
  • PGHOST - The Aurora cluster endpoint
  • PGPORT - The database port (typically 5432)
  • PGUSER - The database user
  • PGDATABASE - The database name
  • VERCEL_OIDC_TOKEN - The OIDC token for local development

Copy link to heading3. Install dependencies

Aurora Serverless is fully PostgreSQL compatible. Install the required packages:

terminal
npm install pg @aws-sdk/rds-signer @vercel/functions dotenv
npm install -D @types/pg tsx

Copy link to heading4. Implement the database connection

Create a lib/db/db.ts file to handle the connection pool and IAM authentication:

lib/db/db.ts
import { config } from "dotenv";
config({ path: ".env.local" });
import { awsCredentialsProvider } from "@vercel/functions/oidc";
import { attachDatabasePool } from "@vercel/functions";
import { Signer } from "@aws-sdk/rds-signer";
import { ClientBase, Pool } from "pg";
const signer = new Signer({
hostname: process.env.PGHOST,
port: Number(process.env.PGPORT),
username: process.env.PGUSER,
region: process.env.AWS_REGION,
credentials: awsCredentialsProvider({
roleArn: process.env.AWS_ROLE_ARN,
clientConfig: { region: process.env.AWS_REGION },
}),
});
const pool = new Pool({
host: process.env.PGHOST,
user: process.env.PGUSER,
database: process.env.PGDATABASE || "postgres",
// The auth token value can be cached for up to 15 minutes (900 seconds) if desired.
password: () => signer.getAuthToken(),
port: Number(process.env.PGPORT),
// Recommended to switch to `true` in production.
// See <https://docs.aws.amazon.com/lambda/latest/dg/services-rds.html#rds-lambda-certificates>
ssl: { rejectUnauthorized: false },
max: 20,
});
attachDatabasePool(pool);
// Single query transaction.
export async function query(sql: string, args: unknown[]) {
return pool.query(sql, args);
}
// Use it for multiple queries transaction.
export async function withConnection<T>(
fn: (client: ClientBase) => Promise<T>,
): Promise<T> {
const client = await pool.connect();
try {
return await fn(client);
} finally {
client.release();
}
}

The dotenv import ensures environment variables are loaded when running scripts locally. The attachDatabasePool utility optimizes connection management in serverless environments.

Copy link to heading5. Test the connection locally

Create a test script to verify your database connection works before building features.

Create lib/db/test-connection.ts:

lib/db/test-connection.ts
import { config } from "dotenv";
config({ path: ".env.local" });
import { awsCredentialsProvider } from "@vercel/functions/oidc";
import { Signer } from "@aws-sdk/rds-signer";
import { Pool } from "pg";
async function testConnection() {
console.log("🔐 Creating RDS signer...");
console.log(` Host: ${process.env.PGHOST}`);
console.log(` User: ${process.env.PGUSER}`);
console.log(` Region: ${process.env.AWS_REGION}`);
const signer = new Signer({
hostname: process.env.PGHOST,
port: Number(process.env.PGPORT),
username: process.env.PGUSER,
region: process.env.AWS_REGION,
credentials: awsCredentialsProvider({
roleArn: process.env.AWS_ROLE_ARN,
clientConfig: { region: process.env.AWS_REGION },
}),
});
console.log("\\n🔑 Generating auth token...");
const token = await signer.getAuthToken();
console.log(` Token generated (length: ${token.length})`);
console.log("\\n🔌 Connecting to database...");
const pool = new Pool({
host: process.env.PGHOST,
user: process.env.PGUSER,
database: process.env.PGDATABASE || "postgres",
password: token,
port: Number(process.env.PGPORT),
ssl: { rejectUnauthorized: false },
});
const result = await pool.query("SELECT NOW() as time, current_database() as db");
console.log(`\\n✅ Connected successfully!`);
console.log(` Database: ${result.rows[0].db}`);
console.log(` Server time: ${result.rows[0].time}`);
await pool.end();
}
testConnection().catch((err) => {
console.error("\\n❌ Connection failed:", err.message);
process.exit(1);
});

Add the script to package.json:

package.json
{
"scripts": {
"db:test": "tsx lib/db/test-connection.ts"
}
}

Run the test:

terminal
npm run db:test

You should see output confirming a successful connection to your Aurora database.

Copy link to heading6. Create tables and seed data

Create a setup script to initialize your database schema and seed data.

Create lib/db/setup.ts:

lib/db/setup.ts
import { config } from "dotenv";
config({ path: ".env.local" });
import { withConnection } from "./db";
async function setup() {
console.log("🔌 Connecting to Aurora PostgreSQL...");
await withConnection(async (client) => {
console.log("📦 Creating movies table...");
await client.query(`
CREATE TABLE IF NOT EXISTS movies (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
score INTEGER NOT NULL DEFAULT 0
)
`);
const { rows } = await client.query("SELECT COUNT(*) as count FROM movies");
const count = parseInt(rows[0].count);
if (count === 0) {
console.log("🌱 Seeding movies...");
await client.query(`
INSERT INTO movies (title, score) VALUES
('The Shawshank Redemption', 93),
('The Godfather', 92),
('The Dark Knight', 90),
('Pulp Fiction', 89),
('Forrest Gump', 88),
('Inception', 87),
('The Matrix', 86),
('Goodfellas', 85),
('Interstellar', 84),
('Fight Club', 83)
`);
console.log("✅ Seeded 10 movies!");
} else {
console.log(`ℹ️ Table already has ${count} movies, skipping seed.`);
}
});
console.log("✅ Setup complete!");
}
setup().catch((err) => {
console.error("❌ Setup failed:", err);
process.exit(1);
});

Add the script to package.json:

package.json
{
"scripts": {
"db:test": "tsx lib/db/test-connection.ts",
"db:setup": "tsx lib/db/setup.ts"
}
}

Run the setup:

terminal
npm run db:setup

Copy link to heading7. Fetch data in a server component

Now you can use the query function to fetch data directly in your Next.js Server Components.

Copy link to headingCreate a query function

In lib/db/queries.ts, implement a function to fetch movies:

lib/db/queries.ts
import { query } from "./db";
export interface Movie {
id: number;
title: string;
score: number;
}
export async function getMovies() {
const sql = "SELECT id, title, score FROM movies ORDER BY score DESC LIMIT 10";
const result = await query(sql, []);
return result.rows as Movie[];
}

Copy link to headingDisplay data in the page

In app/page.tsx, fetch and render the movies:

app/page.tsx
import { getMovies } from "@/lib/db/queries";
// Force dynamic rendering since we're fetching from a database
export const dynamic = "force-dynamic";
export default async function HomePage() {
const movies = await getMovies();
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">Top Movies</h1>
<ul className="space-y-2">
{movies.map((movie) => (
<li key={movie.id} className="p-4 border rounded shadow-sm">
<span className="font-semibold">{movie.title}</span> — {movie.score}
</li>
))}
</ul>
</main>
);
}

The dynamic = "force-dynamic" export prevents Next.js from attempting to statically generate the page at build time, which would fail without database access.

Copy link to heading8. Run the application

Start the development server:

terminal
npm run dev

Visit http://localhost:3000 to see your movies displayed from the Aurora PostgreSQL database.

Copy link to headingBest practices

To ensure your application remains secure and performant:

  • Use attachDatabasePool: This utility from @vercel/functions helps manage database connections efficiently across multiple serverless function invocations, preventing connection exhaustion.
  • Use withConnection for transactions: When running multiple queries that need to be atomic, use the withConnection helper to ensure they run on the same client.
  • Set Proper SSL: Always use ssl: { rejectUnauthorized: false } (or provide the RDS CA certificate) when connecting to AWS RDS to ensure the connection is encrypted.
  • Principle of Least Privilege: Ensure the IAM role assumed by your Vercel project only has the rds-db:connect permission for the specific database user and resource.

Copy link to headingConclusion

You have successfully connected a Next.js application to AWS Aurora PostgreSQL using Vercel's secure OIDC Federation and RDS IAM authentication. This setup eliminates the need for long-lived database passwords, providing a more secure and maintainable architecture. By using Server Components and Vercel's database utilities, you've built a performant data-fetching layer that scales automatically with your application.

Copy link to headingNext steps