AWS PlanetScale Drizzle Postgres
In this example, we use PlanetScale Postgres with a branch-per-stage pattern. Every stage gets its own database branch — so each PR can have an isolated database.
const db = planetscale.getDatabasePostgresOutput({ id: "mydb", organization: "myorg",});
const branch = $app.stage === "production" ? planetscale.getPostgresBranchOutput({ id: db.defaultBranch, organization: db.organization, database: db.name, }) : new planetscale.PostgresBranch("DatabaseBranch", { database: db.name, organization: db.organization, name: $app.stage, parentBranch: db.defaultBranch, });We then create a role and wrap it in a Linkable to link it to a function.
new sst.aws.Function("Api", { handler: "src/api.handler", link: [database], url: true,});You can push your Drizzle schema changes to PlanetScale with:
bun run db:pushThe database role sets successor to postgres so PlanetScale can reassign
owned schema objects before deleting a non-production branch role.
In the function we use Drizzle ORM with the
Resource helper.
import { drizzle } from "drizzle-orm/postgres-js";import { Resource } from "sst";import postgres from "postgres";
const client = postgres({ host: Resource.Database.host, username: Resource.Database.username, password: Resource.Database.password, database: Resource.Database.database,});
export const db = drizzle(client);const db = planetscale.getDatabasePostgresOutput({ id: "mydb", organization: "myorg",});
const branch = $app.stage === "production" ? planetscale.getPostgresBranchOutput({ id: db.defaultBranch, organization: db.organization, database: db.name, }) : new planetscale.PostgresBranch("DatabaseBranch", { database: db.name, organization: db.organization, name: $app.stage, parentBranch: db.defaultBranch, });
const role = new planetscale.PostgresBranchRole("DatabaseRole", { database: db.name, organization: db.organization, branch: branch.name, name: `${$app.name}-${$app.stage}`, inheritedRoles: [ "pg_read_all_data", "pg_write_all_data", "postgres", // Only needed for pushing schema changes ], successor: "postgres",});
const database = new sst.Linkable("Database", { properties: { host: role.accessHostUrl, username: role.username, password: role.password, database: role.databaseName, port: 6432, // Use 5432 for direct connection instead of PgBouncer },});
const api = new sst.aws.Function("Api", { handler: "src/api.handler", link: [database], url: true,});import { db } from "./drizzle";import { todosTable } from "./schema";import { APIGatewayProxyHandlerV2 } from "aws-lambda";
export const handler: APIGatewayProxyHandlerV2 = async (evt) => { if (evt.requestContext.http.method === "GET") { const result = await db.select().from(todosTable).execute(); return { statusCode: 200, body: JSON.stringify(result), }; }
if (evt.requestContext.http.method === "POST") { await db.insert(todosTable).values({ title: "new todosTable" }).execute(); return { statusCode: 200, body: "created", }; }
return { statusCode: 404, body: "not found", };};View the full example.