PlanetScale Postgres
This guide shows how to create and manage PlanetScale PostgreSQL databases with automated Drizzle migrations and deploy a Cloudflare Worker to interact with the database using Alchemy.
-
Install dependencies
Add the required dependencies to your project:
Terminal window bun add drizzle-orm postgresbun add -D drizzle-kitTerminal window npm install drizzle-orm postgresnpm install -D drizzle-kitTerminal window pnpm add drizzle-orm postgrespnpm add -D drizzle-kitTerminal window yarn add drizzle-orm postgresyarn add drizzle-kit -
Set credentials
Create a PlanetScale account and get your API credentials. Add them to your
.env
file:PLANETSCALE_ORG_ID=your_organization_idPLANETSCALE_API_TOKEN=your_api_tokenCLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_idCLOUDFLARE_API_TOKEN=your_cloudflare_api_token -
Define schema
Create your PostgreSQL database schema:
src/schema.ts import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";export const users = pgTable("users", {id: uuid().primaryKey().defaultRandom(),email: varchar({ length: 255 }).notNull(),password: varchar({ length: 255 }).notNull(),createdAt: timestamp().notNull().defaultNow(),updatedAt: timestamp().notNull().defaultNow(),}); -
Configure Drizzle
Add Drizzle configuration and scripts:
drizzle.config.ts import { defineConfig } from "drizzle-kit";export default defineConfig({out: "./drizzle",schema: "./src/schema.ts",dialect: "postgresql",dbCredentials: {url: process.env.DATABASE_URL!,},});package.json {"scripts": {"db:generate": "drizzle-kit generate","db:migrate": "drizzle-kit migrate","db:studio": "drizzle-kit studio"}} -
Create Worker code
Create the Cloudflare Worker that will interact with your database:
src/index.ts import { drizzle } from "drizzle-orm/postgres-js";import postgres from "postgres";import { users } from "./schema";interface Env {HYPERDRIVE: Hyperdrive;}export default {async fetch(request: Request, env: Env): Promise<Response> {const sql = postgres(env.HYPERDRIVE.connectionString);const db = drizzle(sql);if (request.method === "GET") {const allUsers = await db.select().from(users);return Response.json(allUsers);}if (request.method === "POST") {const { email, password } = await request.json();const newUser = await db.insert(users).values({ email, password }).returning();return Response.json(newUser[0]);}return new Response("Method not allowed", { status: 405 });},}; -
Create your infrastructure
Create
alchemy.run.ts
to provision PlanetScale PostgreSQL resources and deploy your Worker:/// <reference types="@types/node" />import alchemy from "alchemy";import { Hyperdrive, Worker } from "alchemy/cloudflare";import { Exec } from "alchemy/os";import { Database, Role } from "alchemy/planetscale";const app = await alchemy("planetscale-postgres");// Create the PostgreSQL databaseconst database = await Database("Database", {name: "sample-database",organizationId: alchemy.env.PLANETSCALE_ORG_ID,clusterSize: "PS_10",kind: "postgresql",});// Create a database role with postgres privilegesconst role = await Role("Role", {database,branch: database.defaultBranch,inheritedRoles: ["postgres"],});// Create Hyperdrive configuration for connection poolingconst hyperdrive = await Hyperdrive("Hyperdrive", {origin: role.connectionUrl,caching: { disabled: true },});// Generate Drizzle migrationsawait Exec("DrizzleGenerate", {command: "bun run db:generate",env: {DATABASE_URL: role.connectionUrl,},memoize: {patterns: ["drizzle.config.ts", "src/schema.ts"],},});// Apply migrations to the databaseawait Exec("DrizzleMigrate", {command:process.platform === "win32"? `cmd /C "bun run db:migrate || if %ERRORLEVEL%==9 exit 0 else exit %ERRORLEVEL%"`: `sh -c 'bun run db:migrate || ( [ $? -eq 9 ] && exit 0 ); exit $?'`,env: {DATABASE_URL: role.connectionUrl,},memoize: {patterns: ["drizzle.config.ts", "drizzle/*.sql"],},});// Deploy the Cloudflare Workerexport const worker = await Worker("Worker", {entrypoint: "src/index.ts",compatibility: "node",bindings: {HYPERDRIVE: hyperdrive,},});// Start Drizzle Studio in local developmentif (app.local) {Exec("DrizzleStudio", {command: "bun run db:studio",env: {DATABASE_URL: role.connectionUrl,},});}console.log(worker.url);await app.finalize(); -
Deploy your stack
Run
alchemy.run.ts
to deploy:Terminal window bun alchemy deployTerminal window npx alchemy deployTerminal window pnpm alchemy deployTerminal window yarn alchemy deployIt should log your Worker URL:
Terminal window https://worker.your-subdomain.workers.dev -
Test your API
Test your deployed Worker:
Terminal window # Create a usercurl -X POST https://worker.your-subdomain.workers.dev \-H "Content-Type: application/json" \-d '{"email": "test@example.com", "password": "password123"}'# Get all userscurl https://worker.your-subdomain.workers.dev -
(Optional) Tear down
Clean up all resources created by this stack:
Terminal window bun alchemy destroyTerminal window npx alchemy destroyTerminal window pnpm alchemy destroyTerminal window yarn alchemy destroy