Skip to main content
Neon Postgres Docs
current

Search documentation

Type to search this documentation.

On this pageOverview

Schema migrations with Prisma and Neon

This tutorial walks you through building a Node.js application with Prisma ORM and Neon, focusing on how to create and apply schema migrations. You'll build a simple book catalog API while learning th...

Pre-built prompt for connecting Node/TypeScript applications to Neon using Prisma ORM.

This tutorial walks you through building a Node.js application with Prisma ORM and Neon, focusing on how to create and apply schema migrations. You'll build a simple book catalog API while learning the migration workflow.

Set up a new Node.js project with Express and Prisma:

Bash
mkdir neon-prisma-migrations && cd neon-prisma-migrations
npm init -y
npm pkg set type="module"
npm install express dotenv @prisma/client @prisma/adapter-neon
npm install prisma typescript tsx @types/node --save-dev
npx prisma init

Add both connection strings to your .env file. Get these from your Neon Console by clicking Connect:

ini
# Pooled connection for your application (note the -pooler suffix)
DATABASE_URL="postgresql://[user]:[password]@[endpoint]-pooler.[region].aws.neon.tech/[dbname]?sslmode=require"

# Direct (unpooled) connection for Prisma CLI (migrations, introspection)
DATABASE_URL_UNPOOLED="postgresql://[user]:[password]@[endpoint].[region].aws.neon.tech/[dbname]?sslmode=require"

Update the prisma.config.ts file in your project root:

TypeScript
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  datasource: {
    url: env('DATABASE_URL_UNPOOLED'),
  },
})

Replace the contents of prisma/schema.prisma:

prisma
generator client {
  provider = "prisma-client-js"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model Author {
  id        Int      @id @default(autoincrement())
  name      String
  bio       String?
  createdAt DateTime @default(now()) @map("created_at")
  books     Book[]

  @@map("authors")
}

model Book {
  id        Int      @id @default(autoincrement())
  title     String
  authorId  Int      @map("author_id")
  createdAt DateTime @default(now()) @map("created_at")
  author    Author   @relation(fields: [authorId], references: [id])

  @@map("books")
}

Run the following command to create your initial migration:

Bash
npx prisma migrate dev --name init

This command:

  1. Creates a prisma/migrations folder with SQL migration files
  2. Applies the migration to your Neon database
  3. Generates the Prisma Client

You should see output confirming the migration was applied.

Create src/db.ts to instantiate Prisma Client with the Neon adapter:

TypeScript
import 'dotenv/config'
import { PrismaClient } from './generated/prisma'
import { PrismaNeon } from '@prisma/adapter-neon'

const adapter = new PrismaNeon({
  connectionString: process.env.DATABASE_URL!,
})

export const prisma = new PrismaClient({ adapter })

Create src/seed.ts to populate the database with sample data:

TypeScript
import { prisma } from './db'

async function seed() {
  const authors = [
    {
      name: 'J.R.R. Tolkien',
      bio: 'Creator of Middle-earth and author of The Lord of the Rings.',
      books: {
        create: [
          { title: 'The Hobbit' },
          { title: 'The Fellowship of the Ring' },
          { title: 'The Two Towers' },
        ],
      },
    },
    {
      name: 'George R.R. Martin',
      bio: 'Author of the epic fantasy series A Song of Ice and Fire.',
      books: {
        create: [
          { title: 'A Game of Thrones' },
          { title: 'A Clash of Kings' },
        ],
      },
    },
  ]

  for (const author of authors) {
    await prisma.author.create({ data: author })
  }

  console.log('✅ Database seeded')
}

seed()
  .catch(console.error)
  .finally(() => prisma.$disconnect())

Run the seed script:

Bash
npx tsx src/seed.ts

Create src/index.ts with Express endpoints:

TypeScript
import express from 'express'
import { prisma } from './db'

const app = express()
const port = process.env.PORT || 3000

app.get('/authors', async (req, res) => {
  const authors = await prisma.author.findMany({
    include: { books: true },
  })
  res.json(authors)
})

app.get('/books', async (req, res) => {
  const books = await prisma.book.findMany({
    include: { author: true },
  })
  res.json(books)
})

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`)
})

Add a start script to package.json:

Bash
npm pkg set scripts.start="tsx src/index.ts"

Start the server:

Bash
npm start

Visit http://localhost:3000/authors to see the data.

Now let's add a country field to the Author model to demonstrate the migration workflow.

Modify the Author model in prisma/schema.prisma:

prisma
model Author {
  id        Int      @id @default(autoincrement())
  name      String
  bio       String?
  country   String?
  createdAt DateTime @default(now()) @map("created_at")
  books     Book[]

  @@map("authors")
}
Bash
npx prisma migrate dev --name add-author-country

Prisma creates a new migration file and applies it. The Prisma Client is automatically regenerated.

Restart your server and check http://localhost:3000/authors. Each author now has a country field (set to null for existing records).

The typical workflow for schema changes with Prisma and Neon:

  1. Modify your schema: Update models in prisma/schema.prisma
  2. Generate migration: Run npx prisma migrate dev --name descriptive-name
  3. Review the migration: Check the generated SQL in prisma/migrations/
  4. Test locally: Verify your application works with the changes
  5. Deploy: In production, use npx prisma migrate deploy

Find the complete source code for this tutorial on GitHub:

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu