When working with databases in a Next.js application, it’s common to use Neon, a serverless PostgreSQL provider. However, local development can be tricky when using Neon since it’s a cloud-based service. This guide walks you through setting up a local Neon database proxy and configuring your Next.js application to connect seamlessly.
While Neon is optimized for the cloud, running a database locally is beneficial for development. By using the local-neon-http-proxy
, we can:
We'll use Docker to run both PostgreSQL and the Neon HTTP proxy locally.
Create a docker-compose.yml
file to define the services:
services:
postgres:
image: postgres:17
command: '-d 1'
volumes:
- db_data:/var/lib/postgresql/data
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=main
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 10s
timeout: 5s
retries: 5
neon-proxy:
image: ghcr.io/timowilhelm/local-neon-http-proxy:main
environment:
- PG_CONNECTION_STRING=postgres://postgres:postgres@postgres:5432/main
ports:
- '4444:4444'
depends_on:
postgres:
condition: service_healthy
volumes:
db_data:
This setup:
Runs a PostgreSQL database container.
Starts a Neon proxy to mimic Neon’s behavior locally.
Exposes the database on port
5432
and the proxy on port
4444
.
Run the following command to start the services:
docker compose up -d
To connect your Next.js app to the local Neon database, configure the database connection using @neondatabase/serverless
and drizzle-orm
.
pnpm add @neondatabase/serverless drizzle-orm ws
Create a file (e.g., db.ts
) with the following setup:
import {
neon,
neonConfig,
type NeonQueryFunction,
Pool,
} from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import { drizzle as drizzleLambda } from "drizzle-orm/neon-serverless";
import * as schema from "./schema";
import ws from "ws";
// Database connection string
let connectionString = process.env.DATABASE_URL!;
if (process.env.NODE_ENV === "development" || process.env.APP_ENV === "local") {
connectionString = "postgres://postgres:postgres@db.localtest.me:5432/main";
neonConfig.fetchEndpoint = (_host) => "http://db.localtest.me:4444/sql"; // Force HTTP
neonConfig.useSecureWebSocket = false; // Disable SSL for local development
neonConfig.wsProxy = "db.localtest.me:4444/v2"; // Ensure correct WebSocket proxy
}
if (process.env.APP_ENV !== "local" && process.env.NODE_ENV === "production") {
neonConfig.webSocketConstructor = ws;
neonConfig.poolQueryViaFetch = true;
}
// Reset neonConfig for each environment
neonConfig.fetchEndpoint = (host) => {
if (
(process.env.NODE_ENV === "development" ||
process.env.APP_ENV === "local") &&
host === "db.localtest.me"
) {
return "http://db.localtest.me:4444/sql";
}
return `https://${host}:443/sql`;
};
export const pool = new Pool({ connectionString });
export const sql = neon(connectionString);
const globalForDb = globalThis as unknown as {
conn: NeonQueryFunction<false, false> | undefined;
};
export const conn = globalForDb.conn ?? sql;
if (process.env.NODE_ENV !== "production") globalForDb.conn = conn;
console.log(process.env.NODE_ENV, "NODE_ENV");
// When querying from the edge network or serverless functions
export const EdgeDb = drizzle(conn, { schema });
// When using transactions in serverless functions
export const db = drizzleLambda(pool, { schema });
export type DB = typeof db;
Local Development Mode
: If running locally, it forces HTTP connections to the Neon proxy and disables SSL/WebSocket security features.
Production Mode
: Uses the default Neon settings with WebSockets and a secure connection.
Database Clients
:
EdgeDb
: Used for edge functions.
db
: Used for serverless functions needing transactions.
Set your .env.local
file:
DATABASE_URL=postgres://postgres:postgres@db.localtest.me:5432/main
NODE_ENV=development
APP_ENV=local
Start your Next.js app:
pnpm dev
Your Next.js application should now be connected to the locally running Neon database!
By using local-neon-http-proxy
, you can simulate a Neon database locally, allowing for faster development cycles and easier debugging. This setup ensures that your local and production environments remain as close as possible, reducing unexpected issues when deploying.
Give this setup a try and streamline your Next.js and Neon development workflow!