Using Vercel Postgres and developing locally

Jjoostschuur5/6/2023
To confirm, if I want to use Vercel Postgres in production and a local Postgres DB for development, is the best way to just conditionally import drizzle (and other platform specific stuff like sql) from drizzle-orm/vercel-postgres or drizzle-orm/node-postgres based on NODE_ENV?

I see the sample Next.js project has been updated for Vercel Postgres: https://github.com/vercel/examples/blob/main/storage/postgres-drizzle/lib/drizzle.ts
Jjoostschuur5/6/2023
Looks like Vercel Postgres doesn't give you a separate environment in the cloud automatically, and I accidentally started a Pro tier trial instead of going straight to Pro and now I can't even add a new DB on until the trial ends.
ASAndrii Sherman5/6/2023
I guess you can use 1 import from drizzle-orm/vercel-postgres

Difference will be in providing connection config to drizzle

import { sql, createPool } from '@vercel/postgres'

const env = ... // any way you can mark env as local/prod. Maybe just process.env.STAGE

export const db = drizzle(env === 'LOCAL' ? createPool({<here will go simple node-postgres config>}) : sql)
Jjoostschuur5/6/2023
You actually do get a connection string from Vercel, so I could just treat this as any other Postgres DB without using the Vercel adapter
Jjoostschuur5/6/2023
Does @vercel/postgres do anything fancy like connection pooling that I'd be missing out on?
Jjoostschuur5/6/2023
Although the solution above looks easy enough
ASAndrii Sherman5/6/2023
yeah, as long as sql from Vercel will cover connection configs
ASAndrii Sherman5/6/2023
and for local you just specify anything you need
ASAndrii Sherman5/6/2023
looking into implementation
ASAndrii Sherman5/6/2023
sql from Vercel is VercelPool type
Jjoostschuur5/6/2023
Alright, will give this a shot shortly. Thanks!

I don't need to do any postinstall generation of stuff like Prisma needs, right? I'm already doing await migrate(db, { migrationsFolder: './drizzle' });
ASAndrii Sherman5/6/2023
which is extends from node-postgres Pool
ASAndrii Sherman5/6/2023
yeah, just run migration on db and that's should be it
Jjoostschuur5/6/2023
Cool. I can't keep asking on the SST Discord how to get Prisma working again in a monorepo ;0
Jjoostschuur5/6/2023
Dax might hurt my feelings
ASAndrii Sherman5/6/2023
just use Drizzle 😅
ASAndrii Sherman5/6/2023
if something won't work - feel free to ping us here
Jjoostschuur5/6/2023
BTW, I was happy to get this random ordering working

export function getCards() {
  return db
    .select()
    .from(languageCardSchema)
    .orderBy(sql<number>`random()`)
    .limit(5);
}


Found it in some test file when searching the repo for 'random', since it wasn't in the main docs.
ASAndrii Sherman5/6/2023
nice! also I think you can skip <number> in orderBy sql
ASAndrii Sherman5/6/2023
sql type is useful for using in select to help infer response type properly

select({ id: sql<number>`count(*)`})
ASAndrii Sherman5/6/2023
just small tip
Jjoostschuur5/6/2023
Gotcha
ASAndrii Sherman5/6/2023
it all will be in web docs
Jjoostschuur5/6/2023
On, one last question. I manually added UNIQUE to my migration file because it looks like that's not yet supported. But there's something in GH issues here for 'add check constraints to docs' is that related to another way to do this for now? https://github.com/drizzle-team/drizzle-orm/issues/310
Jjoostschuur5/6/2023
I think I've given you a hard time about the docs in the past on Twitter. Looking forward to the new ones!
ASAndrii Sherman5/6/2023
yeah, UNIQUE constraints are not yet supported. But will be soon
ASAndrii Sherman5/6/2023
you did right thing to add it manually to sql migration file
ASAndrii Sherman5/6/2023
just be sure to modify sql file before applying to database
ASAndrii Sherman5/6/2023
it's the only restriction
ASAndrii Sherman5/6/2023
other than that you can do with sql file anything you want
Jjoostschuur5/6/2023
Yeah, I just realised that I started my Drizzle folder from scratch again and forgot to reapply it to the latest migration file.
Jjoostschuur5/6/2023
Alright, will try this Vercel Postgres thing later. Plenty of progress already this morning. Thanks again!
ASAndrii Sherman5/6/2023
🫡
Jjoostschuur5/6/2023
Oh, I guess one more comment: It took me a little bit of digging until I figured out you can do this with chaning .array like this. I thought I had to start with array(...) e.g.

  vocabulary: text('vocabulary').array().array().notNull(),
ASAndrii Sherman5/6/2023
yeah, we tried to copy sql syntax
ASAndrii Sherman5/6/2023
so you write text and then [] []
ASAndrii Sherman5/6/2023
same here
ASAndrii Sherman5/6/2023
text.array.array
Jjoostschuur5/6/2023
Will keep that in mind. Have been living in Prisma land for so long.
Jjoostschuur5/6/2023
👋
ASAndrii Sherman5/6/2023
I mean you can add array not only to text
ASAndrii Sherman5/6/2023
so it's easier to just have .array function in any type vs array + duplicating all types in builder
ASAndrii Sherman5/6/2023
so IDE suggestions will become a mess
Jjoostschuur5/6/2023
Should it look something like this?

import { createPool, sql } from '@vercel/postgres';
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { migrate } from 'drizzle-orm/vercel-postgres/migrator';

export const db = drizzle(
  process.env.NODE_ENV === 'production'
    ? sql
    : createPool({
        connectionString: process.env.POSTGRES_URL,
      })
);


That doesn't seem to work

n [VercelPostgresError]: VercelPostgresError - 'invalid_connection_string': This connection string is meant to be used with a direct connection. Make sure to use a pooled connection string or try `createClient()` instead.
Jjoostschuur5/6/2023
This also seems to have stooped working:

.orderBy(sql`random()`)


No overload matches this call.
  Overload 1 of 2, '(builder: (aliases: { id: PgSerial<{ tableName: "language_cards"; name: "id"; data: number; driverParam: number; hasDefault: true; notNull: true; }>; hskLevel: PgSmallInt<{ tableName: "language_cards"; name: "hsklevel"; data: number; driverParam: string | number; hasDefault: false; notNull: true; }>; ... 6 more ...; audio: PgText<...>; }) => ValueOrArray<...>): PgSelect<...>', gave the following error.
    Argument of type 'Promise<QueryResult<QueryResultRow>>' is not assignable to parameter of type '(aliases: { id: PgSerial<{ tableName: "language_cards"; name: "id"; data: number; driverParam: number; hasDefault: true; notNull: true; }>; hskLevel: PgSmallInt<{ tableName: "language_cards"; name: "hsklevel"; data: number; driverParam: string | number; hasDefault: false; notNull: true; }>; ... 6 more ...; audio: Pg...'.
      Type 'Promise<QueryResult<QueryResultRow>>' provides no match for the signature '(aliases: { id: PgSerial<{ tableName: "language_cards"; name: "id"; data: number; driverParam: number; hasDefault: true; notNull: true; }>; hskLevel: PgSmallInt<{ tableName: "language_cards"; name: "hsklevel"; data: number; driverParam: string | number; hasDefault: false; notNull: true; }>; ... 6 more ...; audio: PgText<...>; }): ValueOrArray<...>'.
  Overload 2 of 2, '(...columns: (SQL<unknown> | AnyPgColumn<{}> | Aliased<unknown>)[]): PgSelect<"language_cards", { id: PgSerial<{ tableName: "language_cards"; name: "id"; data: number; driverParam: number; hasDefault: true; notNull: true; }>; ... 7 more ...; audio: PgText<...>; }, "single", Record<...>>', gave the following error.
    Argument of type 'Promise<QueryResult<QueryResultRow>>' is not assignable to parameter of type 'SQL<unknown> | AnyPgColumn<{}> | Aliased<unknown>'.
ASAndrii Sherman5/6/2023
Try to use createClient function from Vercel
ASAndrii Sherman5/6/2023
Instead of createPool
ASAndrii Sherman5/6/2023
It should be imported from same path
Jjoostschuur5/6/2023
Anything that hits the DB seems to cause it to just exit silently. At first it exited without any errors when it checked for migrations, then when I commented that out and reran it, it happened again the moment I try and write to the DB

import { createClient, sql } from '@vercel/postgres';
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/vercel-postgres';
import { migrate } from 'drizzle-orm/vercel-postgres/migrator';

export const db = drizzle(
  process.env.NODE_ENV === 'production'
    ? sql
    : createClient({
        connectionString: process.env.POSTGRES_URL,
      })
);
ASAndrii Sherman5/6/2023
So no errors now? Just nothing happens on query execution?
Jjoostschuur5/6/2023
Yeah, I wrapped the migrate command in some quick console log debug statements and it just exits right in the middle. Not even a non zero exit code
ASAndrii Sherman5/6/2023
That’s something I may help you tomorrow. It’s late here already
Will get back to you tomorrow with suggestions and trying to reproduce it locally

You can check, that migration path was proper and also drizzle function has 2nd param with logger: true config to enable all query logs in terminal
ASAndrii Sherman5/6/2023
If nothing will help, I’ll be here tomorrow
Jjoostschuur5/6/2023
No worries. Also late here too (London)
Jjoostschuur5/6/2023
Thanks for trying to figure this out with me
ASAndrii Sherman5/6/2023
🫡 :drizzle:
Jjoostschuur5/6/2023
(for tomorrow)

With the logger enabled, the only output from that I see is the query it tries to run before it silently exits.

The migration path is fine, since I used this exact code to migrate for NODE_ENV === 'production' and that created the table in the non local Vercel Postgres DB just fine.
Jjoostschuur5/7/2023
So my randomisation problem was down to me not understanding server components and SSR. It was statically creating the page based on a random list at runtime.

In the end I fixed it by using the new server action feature:

import { getRandomCards } from '~/db/db';

async function getCards() {
  'use server';

  return getRandomCards();
}

export default async function FlashCards() {
  const cards = await getCards();

  return ...
}
Jjoostschuur5/7/2023
Well. This explains the main issue of using a local DB: It'll return that error if my URL doesn't satisfy this check.

export function isPooledConnectionString(connectionString: string): boolean {
  return connectionString.includes('-pooler.');
}


Hah! Guess I can rename my local URL to include that anywhere.

https://github.com/vercel/storage/blob/8b51d484e3a0b3e3f0f487e7e6313b5a8274dd50/packages/postgres/src/postgres-connection-string.ts#L33
Jjoostschuur5/7/2023
For now, I just worked around this by using each adapter/migrator separately:

import { sql } from '@vercel/postgres';
import { eq, sql as sqlDrizzle } from 'drizzle-orm';
import { drizzle as drizzleNode } from 'drizzle-orm/node-postgres';
import { drizzle as drizzleVercel } from 'drizzle-orm/vercel-postgres';
import { migrate as migrateNode } from 'drizzle-orm/node-postgres/migrator';
import { migrate as migrateVercel } from 'drizzle-orm/vercel-postgres/migrator';

import { Pool } from 'pg';

import { languageCard, NewLanguageCard } from './schema';

console.log('process.env.NODE_ENV', process.env.NODE_ENV);
console.log('process.env.POSTGRES_URL', process.env.POSTGRES_URL);

const db =
  process.env.NODE_ENV === 'production'
    ? drizzleVercel(sql)
    : drizzleNode(new Pool({ connectionString: process.env.POSTGRES_URL }));

export async function dbMigrate() {
  if (process.env.NODE_ENV === 'production')
    await migrateVercel(db, { migrationsFolder: './drizzle' });
  else await migrateNode(db, { migrationsFolder: './drizzle' });
}
Jjoostschuur5/7/2023
So I think this wraps up this thread.
Jjoostschuur5/8/2023
For future reference, there is mention of a PR that would help here: https://github.com/vercel/storage/issues/123#issuecomment-1537719772
M2McLean 255/15/2023
@joostschuur Curious how you invoked your migrations from a Vercel project?
Jjoostschuur5/15/2023
Whenever my lambda or util script fires up, it calls this migrate function. I just use migrator from 'drizzle-orm/vercel-postgres/migrator for production instead of the one from drizzle-orm/node-postgres/migrator for local dev. Bit of a workaround for now, but it works.

https://github.com/jschuur/learnchinese.club/blob/4eca033b376aea76a6aedbb86c1d316e48e0bc82/src/db/db.ts#L18