How to Set Up a Serverless Postgres Database with Neon and Drizzle
Set up a serverless Postgres database with Neon and Drizzle ORM: schema, migrations, and queries without managing servers.
Serverless Postgres in minutes
Neon gives you a Postgres database with no server to provision — it scales to zero and branches like Git. Drizzle is a lightweight TypeScript ORM that maps your tables to types you can use end to end.
Step 1 — Create a Neon project
- Sign up at neon.tech and create a project.
- Copy the connection string — it looks like
postgresql://user:[email protected]/neondb?sslmode=require. - Store it as an environment variable, never in source:
DATABASE_URL="postgresql://..."
Step 2 — Install Drizzle
npm install drizzle-orm postgres
npm install -D drizzle-kit
Step 3 — Define a schema
src/db/schema.ts:
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
body: text("body").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
drizzle.config.ts:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: { url: process.env.DATABASE_URL! },
});
Step 4 — Generate and apply migrations
npx drizzle-kit generate
npx drizzle-kit migrate
generate diffs your schema into SQL files; migrate applies them to Neon.
Commit the generated files — they’re your schema’s history.
Step 5 — Use Neon branches for staging
Neon’s killer feature is branching: you can fork your database instantly, which makes it practical to give every PR its own Postgres. The workflow looks like this:
- Branch the
maindatabase from the Neon dashboard or CLI. - Point your staging environment at the branch’s connection string.
- Merge the branch back to
mainwhen the PR lands.
Because migrations are plain SQL files (from drizzle-kit generate), applying
them to a fresh branch is the same npx drizzle-kit migrate — so a branch
never drifts from production.
Step 6 — Query with type safety
src/db/index.ts:
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { posts } from "./schema";
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client);
export async function listPosts() {
return db.select().from(posts).orderBy(posts.createdAt);
}
The return type is inferred from the schema — rename a column and TypeScript flags every stale query.
Connection pooling for serverless
Serverless runtimes (Workers, serverless functions) open a new connection per invocation, which can exhaust Neon’s connection limit under load. Neon provides a pooled connection string for exactly this: same host, but it routes through PgBouncer-style pooling and lets thousands of short-lived connections share a handful of real ones.
Use the pooled URL in production serverless code, and the direct URL for migrations and local development. Drizzle doesn’t care which one you hand it — the connection string alone determines the behavior.
Troubleshooting
connect ECONNREFUSED/ SSL errors — ensure the URL ends with?sslmode=require.- Migration drift — never edit the database by hand; change the schema, regenerate, and migrate.
- Too many connections — use Neon’s pooled connection string for serverless runtimes so you don’t exhaust the connection limit.
Summary
You have a managed Postgres database, a type-safe schema, and versioned migrations — with zero servers to operate. Put the migration command in CI and keep the database URL in the deployment environment, never in the repository. For a repeatable deployment pipeline, pair this guide with GitHub Actions for CI/CD.
Comments
One comment per thread every 30 minutes · edits are unlimited.