/ astro-integrations / How to Integrate PlanetScale with Astro: Complete Guide
astro-integrations 5 min read

How to Integrate PlanetScale with Astro: Complete Guide

Step-by-step guide to integrating PlanetScale with your Astro website. Setup, configuration, and best practices.

How to Integrate PlanetScale with Astro: Complete Guide

PlanetScale is a serverless MySQL-compatible database platform built on Vitess, the same technology that powers YouTube's database infrastructure. When paired with Astro, it gives you a production-grade database backend for dynamic pages, API routes, and server-side rendered content without managing database servers yourself.

This guide walks you through connecting PlanetScale to an Astro project from scratch, covering installation, configuration, querying data, and deploying to production.

Prerequisites

Before starting, make sure you have the following ready:

  • Node.js 18 or later installed on your machine
  • An existing Astro project (v3.0 or newer recommended)
  • A PlanetScale account (free tier available at planetscale.com)
  • A PlanetScale database already created through the dashboard
  • Basic familiarity with SQL and JavaScript/TypeScript

You should also have Astro configured for server-side rendering or hybrid mode, since database queries run on the server.

Installation

Start by installing the PlanetScale serverless driver, which communicates with your database over HTTP instead of traditional TCP connections. This makes it compatible with serverless and edge environments.

npm install @planetscale/database

If you prefer using an ORM layer on top, you can also install Drizzle ORM or Kysely, but the native driver works well for most use cases.

Next, make sure your Astro project uses an SSR adapter. If you have not set one up yet:

npx astro add node

This adds the Node.js adapter so your server-side code can execute database queries.

Configuration

Setting Up Environment Variables

Create or update your .env file in the project root with your PlanetScale connection credentials. You can find these in the PlanetScale dashboard under your database's "Connect" section. Select the @planetscale/database option for the connection string format.

DATABASE_HOST=aws.connect.psdb.cloud
DATABASE_USERNAME=your_username_here
DATABASE_PASSWORD=pscale_pw_your_password_here

Creating the Database Client

Create a utility file to initialize the PlanetScale connection. This keeps your database logic centralized and reusable across your project.

// src/lib/db.ts
import { connect } from '@planetscale/database';

const config = {
  host: import.meta.env.DATABASE_HOST,
  username: import.meta.env.DATABASE_USERNAME,
  password: import.meta.env.DATABASE_PASSWORD,
};

export const db = connect(config);

The connect function creates a lightweight client that makes HTTP requests to PlanetScale's edge network. There is no persistent connection to manage.

Updating Astro Config

Make sure your astro.config.mjs is set to server or hybrid output mode:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'hybrid',
  adapter: node({
    mode: 'standalone',
  }),
});

Hybrid mode lets you keep static pages where possible while enabling server-side rendering for pages that need database access.

Common Patterns

Querying Data in Astro Pages

Use the database client inside .astro page frontmatter or API routes. Here is an example page that fetches blog posts from a posts table:

---
// src/pages/blog.astro
import { db } from '../lib/db';

const result = await db.execute('SELECT id, title, slug, published_at FROM posts WHERE status = ? ORDER BY published_at DESC', ['published']);
const posts = result.rows;
---

<html>
  <body>
    <h1>Blog Posts</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/blog/${post.slug}`}>{post.title}</a>
        </li>
      ))}
    </ul>
  </body>
</html>

Always use parameterized queries (the ? placeholder syntax) to prevent SQL injection.

Building API Routes

You can also create JSON API endpoints that query PlanetScale:

// src/pages/api/posts.ts
import type { APIRoute } from 'astro';
import { db } from '../../lib/db';

export const GET: APIRoute = async () => {
  const result = await db.execute('SELECT id, title, slug FROM posts LIMIT 20');

  return new Response(JSON.stringify(result.rows), {
    headers: { 'Content-Type': 'application/json' },
  });
};

Using PlanetScale Branching

PlanetScale supports database branching, which works similarly to Git branches. Create a development branch for schema changes, then merge it into production through a deploy request. Use separate connection credentials for each branch in your .env.development and .env.production files:

# .env.development
DATABASE_HOST=aws.connect.psdb.cloud
DATABASE_USERNAME=dev_branch_user
DATABASE_PASSWORD=pscale_pw_dev_password

This prevents accidental schema changes on your production database.

Troubleshooting

Connection errors with "fetch is not defined": The PlanetScale driver uses the Fetch API internally. Make sure you are running Node.js 18 or later, which includes a global fetch implementation. If you are stuck on an older version, install undici and set it as a global polyfill.

Queries return empty results: Double-check that your connection credentials point to the correct database branch. PlanetScale defaults to the main branch, but your data might be on a development branch.

Environment variables are undefined: Astro requires server-side environment variables to be accessed through import.meta.env and they must be prefixed or explicitly listed in your Astro config. Verify that your .env file is in the project root and restart the dev server after adding new variables.

Slow query performance: PlanetScale queries go through HTTP, which adds slight latency compared to TCP connections. For pages with multiple queries, run them in parallel using Promise.all rather than sequentially. Also add proper indexes to your tables through the PlanetScale dashboard.

Type safety concerns: The raw driver returns untyped rows. If you want full TypeScript type safety, consider layering Drizzle ORM or Kysely on top of the PlanetScale driver. Both support PlanetScale as a connection target and provide typed query builders.

Conclusion

PlanetScale pairs well with Astro for projects that need a reliable MySQL database without the overhead of managing servers or connection pools. The HTTP-based driver works seamlessly in serverless and edge environments, and features like database branching give you safe schema migration workflows.

Start with the native driver for simple projects. As your application grows, add an ORM layer for type safety and query building. With hybrid rendering in Astro, you can keep your marketing pages static and fast while powering dynamic features with PlanetScale on the server side.