Connecting a Nextjs Application to a Local Neon Database

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.

Next.jsTypeScriptPostgreSQLDrizzleDatabase DesignTutorial

Why Use a Local Neon Proxy?

While Neon is optimized for the cloud, running a database locally is beneficial for development. By using the local-neon-http-proxy, we can:

  • Mimic Neon’s HTTP and WebSocket-based interactions.
  • Develop and test database queries without relying on a remote Neon instance.
  • Reduce latency when working locally.

Setting Up a Local PostgreSQL Instance with Neon Proxy

We'll use Docker to run both PostgreSQL and the Neon HTTP proxy locally.

Docker Compose Configuration

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

Configuring the Next.js Application

To connect your Next.js app to the local Neon database, configure the database connection using @neondatabase/serverless and drizzle-orm.

Install Dependencies

pnpm add @neondatabase/serverless drizzle-orm ws

Database Connection Code

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;

Explanation

  • 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.

Running the Application

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!

Conclusion

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!