---
title: Schema migration with Neon Postgres and Drizzle ORM
subtitle: Set up Neon Postgres and run migrations for your TypeScript project using
Drizzle ORM
enableTableOfContents: true
updatedOn: '2025-06-30T11:30:21.898Z'
---
[Drizzle](https://orm.drizzle.team/) is a TypeScript-first ORM that connects to all major databases and works across most Javascript runtimes. It provides a simple way to define database schemas and queries in an SQL-like dialect and tools to generate and run migrations.
This guide shows how to use `Drizzle` with the `Neon` Postgres database in a Typescript project. We'll create a simple Node.js application with `Hono.js` and demonstrate the full workflow of setting up and working with your database using `Drizzle`.
## Prerequisites
To follow along with this guide, you will need:
- A Neon account. If you do not have one, sign up at [Neon](https://neon.tech). Your Neon project comes with a ready-to-use Postgres database named `neondb`. We'll use this database in the following examples.
- [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed on your local machine. We'll use Node.js to build and test the application locally.
## Setting up your Neon database
### Initialize a new project
1. Log in to the Neon Console and navigate to the [Projects](https://console.neon.tech/app/projects) section.
2. Select a project or click the `New Project` button to create a new one.
### Retrieve your Neon database connection string
Find your database connection string by clicking the **Connect** button on your **Project Dashboard** to open the **Connect to your database** modal. It should look similar to this:
```bash
postgresql://alex:AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech/dbname?sslmode=require&channel_binding=require
```
Keep your connection string handy for later use.
Neon supports both direct and pooled database connection strings, which you can find by clicking the **Connect** button on your **Project Dashboard** to open the **Connect to your database** modal. A pooled connection string connects your application to the database via a PgBouncer connection pool, allowing for a higher number of concurrent connections. However, using a pooled connection string for migrations can lead to errors. For this reason, we recommend using a direct (non-pooled) connection when performing migrations. For more information about direct and pooled connections, see [Connection pooling](/docs/connect/connection-pooling).
## Setting up the TypeScript application
### Create a new Hono.js project
We'll create a simple catalog, with API endpoints that query the database for authors and a list of their books. Run the following command in your terminal to set up a new project using `Hono.js`:
```bash
npm create hono@latest neon-drizzle-guide
```
This initiates an interactive CLI prompt to set up a new project. To follow along with this guide, you can use the following settings:
```bash
Need to install the following packages:
create-hono@0.9.0
Ok to proceed? (y) y
create-hono version 0.9.0
✔ Using target directory … neon-drizzle-guide
✔ Which template do you want to use? › nodejs
cloned honojs/starter#main to ./repos/javascript/neon-drizzle-guide
✔ Do you want to install project dependencies? … yes
✔ Which package manager do you want to use? › npm
```
To use Drizzle and connect to the Neon database, we also add the `drizzle-orm` and `drizzle-kit` packages to our project, along with the `Neon serverless` driver library.
```bash
cd neon-drizzle-guide && touch .env
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit dotenv
```
Add the `DATABASE_URL` environment variable to your `.env` file, which you'll use to connect to our Neon database. Use the connection string that you obtained from the Neon Console earlier:
```bash
# .env
DATABASE_URL=NEON_DATABASE_CONNECTION_STRING
```
Test that the starter `Hono.js` application works by running `npm run dev` in the terminal. You should see the `Hello, Hono!` message when you navigate to `http://localhost:3000` in your browser.
### Set up the database schema
Now, we will define the schema for the application using the `Drizzle` ORM. Create a new `schema.ts` file in your `src` directory and add the following code:
```typescript
// src/schema.ts
import { pgTable, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const authors = pgTable('authors', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
bio: text('bio'),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const books = pgTable('books', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').references(() => authors.id),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
```
The code defines two tables: `authors`, which will contain the list of all the authors, and `books`, which will contain the list of books written by the authors. Each book is associated with an author using the `authorId` field.
To generate a migration to create these tables in the database, we'll use the `drizzle-kit` command. Add the following script to the `package.json` file at the root of your project:
```json
{
"scripts": {
"db:generate": "drizzle-kit generate --dialect=postgresql --schema=src/schema.ts --out=./drizzle"
}
}
```
Then, run the following command in your terminal to generate the migration files:
```bash
npm run db:generate
```
This command generates a new folder named `drizzle` containing the migration files for the `authors` and `books` tables.
### Run the migration
The generated migration file is written in SQL and contains the necessary commands to create the tables in the database. To apply these migrations, we'll use the [Neon serverless driver](/docs/serverless/serverless-driver) and helper functions provided by the `drizzle-orm` library.
Create a new `migrate.ts` in your `src` directory and add the following code:
```typescript
// src/migrate.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import { migrate } from 'drizzle-orm/neon-http/migrator';
import { config } from 'dotenv';
config({ path: '.env' });
const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql);
const main = async () => {
try {
await migrate(db, { migrationsFolder: 'drizzle' });
console.log('Migration completed');
} catch (error) {
console.error('Error during migration:', error);
process.exit(1);
}
};
main();
```
The `drizzle-orm` package comes with an integration for `Neon`, which allows us to run the migrations using the `migrate` function. Add a new script to the `package.json` file that executes the migration.
```json
{
"scripts": {
"db:migrate": "tsx ./src/migrate.ts"
}
}
```
You can now run the migration script using the following command:
```bash
npm run db:migrate
```
You should see the `Migration completed` message in the terminal, indicating that the migration was successful.
### Seed the database
To test the application works, we need to add some example data to our tables. Create a new file at `src/seed.ts` and add the following code to it:
```typescript
// src/seed.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import { authors, books } from './schema';
import { config } from 'dotenv';
config({ path: '.env' });
const sql = neon(process.env.DATABASE_URL!);
const db = drizzle(sql);
async function seed() {
await db.insert(authors).values([
{
name: 'J.R.R. Tolkien',
bio: 'The creator of Middle-earth and author of The Lord of the Rings.',
},
{
name: 'George R.R. Martin',
bio: 'The author of the epic fantasy series A Song of Ice and Fire.',
},
{
name: 'J.K. Rowling',
bio: 'The creator of the Harry Potter series.',
},
]);
const authorRows = await db.select().from(authors);
const authorIds = authorRows.map((row) => row.id);
await db.insert(books).values([
{
title: 'The Fellowship of the Ring',
authorId: authorIds[0],
},
{
title: 'The Two Towers',
authorId: authorIds[0],
},
{
title: 'The Return of the King',
authorId: authorIds[0],
},
{
title: 'A Game of Thrones',
authorId: authorIds[1],
},
{
title: 'A Clash of Kings',
authorId: authorIds[1],
},
{
title: "Harry Potter and the Philosopher's Stone",
authorId: authorIds[2],
},
{
title: 'Harry Potter and the Chamber of Secrets',
authorId: authorIds[2],
},
]);
}
async function main() {
try {
await seed();
console.log('Seeding completed');
} catch (error) {
console.error('Error during seeding:', error);
process.exit(1);
}
}
main();
```
This script inserts some seed data into the `authors` and `books` tables. Add a new script to the `package.json` file that runs the seeding program.
```json
{
"scripts": {
"db:seed": "tsx ./src/seed.ts"
}
}
```
Run the seed script using the following command:
```bash
npm run db:seed
```
You should see the `Seeding completed` message in the terminal, indicating that the seed data was inserted into the database.
### Implement the API endpoints
Now that the database is set up and populated with data, we can implement the API to query the authors and their books. Replace the existing `src/index.ts` file with the following code:
```typescript
// src/index.ts
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { env } from 'hono/adapter';
import { config } from 'dotenv';
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import { authors, books } from './schema';
config({ path: '.env' });
const app = new Hono();
app.get('/', (c) => {
return c.text('Hello, this is a catalog of books!');
});
app.get('/authors', async (c) => {
const { DATABASE_URL } = env<{ DATABASE_URL: string }>(c);
const sql = neon(DATABASE_URL);
const db = drizzle(sql);
const output = await db.select().from(authors);
return c.json(output);
});
app.get('/books/:authorId', async (c) => {
const { DATABASE_URL } = env<{ DATABASE_URL: string }>(c);
const sql = neon(DATABASE_URL);
const db = drizzle(sql);
const authorId = c.req.param('authorId');
const output = await db
.select()
.from(books)
.where(eq(books.authorId, Number(authorId)));
return c.json(output);
});
const port = 3000;
console.log(`Server is running on port ${port}`);
serve({
fetch: app.fetch,
port,
});
```
This code sets up a simple API with two endpoints: `/authors` and `/books/:authorId`. The `/authors` endpoint returns a list of all the authors, and the `/books/:authorId` endpoint returns a list of books written by the specific author with the given `authorId`.
Run the application using the following command:
```bash
npm run dev
```
This will start a `Hono.js` server at `http://localhost:3000`. Navigate to `http://localhost:3000/authors` and `http://localhost:3000/books/1` in your browser to check that the API works as expected.
## Migration after a schema change
To demonstrate how to execute a schema change, we'll add a new column to the `authors` table, listing the country of origin for each author.
### Generate the new migration
Modify the code in the `src/schema.ts` file to add the new column to the `authors` table:
```typescript
// src/schema.ts
import { pgTable, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const authors = pgTable('authors', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
bio: text('bio'),
country: text('country'),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
export const books = pgTable('books', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').references(() => authors.id),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
```
Now, we can run the following command to generate a new migration file:
```bash
npm run db:generate
```
This command generates a new migration file in the `drizzle` folder, with the SQL command to add the new column to the `authors` table.
### Run the migration
Run the migration script using the following command:
```bash
npm run db:migrate
```
You should see the `Migration completed` message in the terminal, indicating it was successful.
### Verify the schema change
To verify that the schema change was successful, run the application using the following command:
```bash
npm run dev
```
You can navigate to `http://localhost:3000/authors` in your browser to check that each author entry has a `country` field, currently set to `null`.
## Conclusion
In this guide, we set up a new TypeScript project using `Hono.js` and `Drizzle` ORM and connected it to a `Neon` Postgres database. We created a schema for the database, generated and ran migrations, and implemented API endpoints to query the database.
## Source code
You can find the source code for the application described in this guide on GitHub.
Migrations with Neon and Drizzle
## Resources
For more information on the tools used in this guide, refer to the following resources:
- [Drizzle ORM](https://orm.drizzle.team/)
- [Hono.js](https://hono.dev/)