Claude Code for PlanetScale: Serverless MySQL with Branching — Claude Skills 360 Blog
Blog / AI / Claude Code for PlanetScale: Serverless MySQL with Branching
AI

Claude Code for PlanetScale: Serverless MySQL with Branching

Published: July 31, 2027
Read time: 5 min read
By: Claude Skills 360

PlanetScale is a serverless MySQL database built on Vitess with Git-like branching for schemas — new Client({ url: DATABASE_URL }) from @planetscale/database initializes the HTTP-based edge driver (no TCP connection pool needed). await client.execute("SELECT * FROM users WHERE id = ?", [id]) runs parameterized SQL. connection.transaction(async (tx) => { await tx.execute(...); await tx.execute(...) }) wraps statements in a transaction. Prisma: provider = "mysql" with relationMode = "prisma" (no foreign key DDL). Schema branching: pscale branch create myapp preview forks the schema — make DDL changes, open a deploy request, and PlanetScale applies them non-blocking with no table locking. pscale deploy-request create myapp preview submits the migration. Deploy requests are reviewed like PRs and can be applied with zero downtime. @planetscale/database SDK works in Cloudflare Workers and Vercel Edge Functions — no connection pool config needed. Drizzle ORM: drizzle(connection, { schema }) with the PlanetScale HTTP driver. pscale data dump and pscale data load for import/export. Claude Code generates PlanetScale schema, Prisma and Drizzle setup, and edge-compatible query patterns.

CLAUDE.md for PlanetScale

## PlanetScale Stack
- Edge driver: @planetscale/database >= 1.x — HTTP-based, works in Cloudflare Workers and Edge runtimes
- Init: import { connect } from "@planetscale/database"; const conn = connect({ url: process.env.DATABASE_URL! })
- Query: const { rows } = await conn.execute("SELECT * FROM users WHERE id = ?", [id])
- Transaction: await conn.transaction(async (tx) => { await tx.execute(...) })
- Prisma: provider = "mysql", add relationMode = "prisma" in datasource block (Vitess has no FK DDL)
- Drizzle: import { drizzle } from "drizzle-orm/planetscale-serverless"; const db = drizzle(conn, { schema })
- Branching: pscale branch create APP BRANCH → develop → pscale deploy-request create APP BRANCH

PlanetScale Connection

// lib/planetscale/client.ts — PlanetScale edge-compatible client
import { connect, type Connection } from "@planetscale/database"

function createConnection(): Connection {
  return connect({
    url:      process.env.DATABASE_URL!,
    fetch:    globalThis.fetch,          // use native fetch (edge-compatible)
  })
}

// Singleton for Node.js; always create new for edge workers
const globalConn = globalThis as unknown as { psConn?: Connection }

export function getConnection(): Connection {
  if (process.env.NEXT_RUNTIME === "edge") {
    // Edge: create a new connection per request (no persistent TCP)
    return createConnection()
  }
  // Node.js: reuse connection
  if (!globalConn.psConn) globalConn.psConn = createConnection()
  return globalConn.psConn
}

// ── Type-safe query helpers ────────────────────────────────────────────────

export type QueryResult<T> = {
  rows: T[]
  rowsAffected: number
  insertId: string
}

export async function query<T>(
  sql: string,
  args: unknown[] = [],
): Promise<T[]> {
  const conn  = getConnection()
  const result = await conn.execute(sql, args, { as: "object" })
  return result.rows as T[]
}

export async function execute(
  sql: string,
  args: unknown[] = [],
): Promise<{ rowsAffected: number; insertId: string }> {
  const conn   = getConnection()
  const result = await conn.execute(sql, args)
  return { rowsAffected: result.rowsAffected, insertId: result.insertId }
}

export async function transact<T>(
  fn: (tx: Connection) => Promise<T>,
): Promise<T> {
  const conn = getConnection()
  return conn.transaction(fn)
}

Prisma Schema for PlanetScale

// prisma/schema.prisma — PlanetScale / Vitess compatible
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"  // Required: Vitess doesn't support FK constraints
}

model User {
  id        String    @id @default(cuid())
  email     String    @unique
  name      String?
  plan      String    @default("free")
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  orders    Order[]

  @@index([email])
}

model Order {
  id        String      @id @default(cuid())
  userId    String
  user      User        @relation(fields: [userId], references: [id])
  amount    Decimal     @db.Decimal(10, 2)
  status    String      @default("pending")
  createdAt DateTime    @default(now())

  @@index([userId])  // Required: all FK-like columns need explicit indexes in Vitess
  @@index([status])
}

model Product {
  id          String   @id @default(cuid())
  sku         String   @unique
  name        String
  description String?  @db.Text
  price       Decimal  @db.Decimal(10, 2)
  stock       Int      @default(0)
  createdAt   DateTime @default(now())

  @@index([sku])
  @@fulltext([name, description])  // MySQL FULLTEXT for search
}

Drizzle ORM with PlanetScale

// lib/planetscale/drizzle.ts — Drizzle ORM with PlanetScale serverless driver
import { drizzle } from "drizzle-orm/planetscale-serverless"
import { connect } from "@planetscale/database"
import { mysqlTable, varchar, decimal, int, boolean, timestamp, text } from "drizzle-orm/mysql-core"
import { eq, and, like, gte, lte, desc } from "drizzle-orm"

const connection = connect({ url: process.env.DATABASE_URL! })

// ── Schema ─────────────────────────────────────────────────────────────────

export const users = mysqlTable("users", {
  id:        varchar("id", { length: 128 }).primaryKey(),
  email:     varchar("email", { length: 255 }).notNull().unique(),
  name:      varchar("name", { length: 255 }),
  plan:      varchar("plan", { length: 50 }).notNull().default("free"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
})

export const products = mysqlTable("products", {
  id:          varchar("id", { length: 128 }).primaryKey(),
  sku:         varchar("sku", { length: 100 }).notNull().unique(),
  name:        varchar("name", { length: 255 }).notNull(),
  description: text("description"),
  price:       decimal("price", { precision: 10, scale: 2 }).notNull(),
  stock:       int("stock").notNull().default(0),
  inStock:     boolean("in_stock").notNull().default(true),
  createdAt:   timestamp("created_at").defaultNow().notNull(),
})

export const db = drizzle(connection, { schema: { users, products } })

// ── Queries ────────────────────────────────────────────────────────────────

export async function getUserByEmail(email: string) {
  return db.select().from(users).where(eq(users.email, email)).limit(1)
    .then((rows) => rows[0] ?? null)
}

export async function searchProducts(
  query: string,
  minPrice?: number,
  maxPrice?: number,
): Promise<typeof products.$inferSelect[]> {
  const conditions = [like(products.name, `%${query}%`)]
  if (minPrice !== undefined) conditions.push(gte(products.price, String(minPrice)))
  if (maxPrice !== undefined) conditions.push(lte(products.price, String(maxPrice)))

  return db.select().from(products)
    .where(and(...conditions))
    .orderBy(desc(products.createdAt))
    .limit(20)
}

Next.js Edge API Route

// app/api/users/[id]/route.ts — edge-compatible PlanetScale query
export const runtime = "edge"

import { NextResponse } from "next/server"
import { query } from "@/lib/planetscale/client"

type User = { id: string; email: string; name: string; plan: string }

export async function GET(
  _req: Request,
  { params }: { params: { id: string } },
) {
  const users = await query<User>(
    "SELECT id, email, name, plan FROM users WHERE id = ? LIMIT 1",
    [params.id],
  )

  if (users.length === 0) {
    return NextResponse.json({ error: "Not found" }, { status: 404 })
  }

  return NextResponse.json(users[0])
}

For the Neon alternative when needing a PostgreSQL-compatible serverless database with database branching, scale to zero, and a standard pg/Prisma PostgreSQL provider — Neon uses PostgreSQL wire protocol with branching for preview environments, while PlanetScale uses MySQL wire protocol with Vitess-powered branching and non-blocking schema changes via deploy requests, see the Neon guide. For the Supabase alternative when needing a full Postgres-based BaaS with Auth, Realtime subscriptions, Storage, Edge Functions, and a rich PostgreSQL extension ecosystem beyond just the database — Supabase is the full open-source Firebase alternative while PlanetScale focuses exclusively on the best-in-class serverless MySQL database experience, see the Supabase guide. The Claude Skills 360 bundle includes PlanetScale skill sets covering Prisma, Drizzle, and edge-compatible queries. Start with the free tier to try serverless MySQL generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free