Connect Next.js to AWS Aurora PostgreSQL using Vercel Marketplace

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

Michael Toth
Ismael Rumzan
7 min read
Last updated January 15, 2026

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.

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

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

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.

Before you begin, make sure you have:

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

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.

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.

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

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

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.

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.

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

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

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[];
}

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.

Start the development server:

terminal
npm run dev

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

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.

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.

Was this helpful?

supported.

Read related documentation

Explore more Storage Integrations guides

No related guides available.

Connect Next.js to AWS Aurora PostgreSQL using Vercel Marketplace | Vercel Knowledge Base