Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with MySQL

MySQL's mysql2 driver uses TCP protocol and fails in Bolt's WebContainer during development. Three options: (1) PlanetScale's @planetscale/database package uses HTTP and works in the WebContainer for development; (2) Deploy first, then mysql2 works normally in API routes; (3) Prisma ORM with PlanetScale adapter for type-safe queries. For new projects with no existing MySQL dependency, Supabase (PostgreSQL) is the recommended path of least resistance.

What you'll learn

  • Why mysql2 fails in Bolt's WebContainer and which approach works during development
  • How to use PlanetScale's HTTP-based MySQL client that works in the WebContainer
  • How to build Next.js API routes that query MySQL with proper connection pooling
  • How to use Prisma ORM with PlanetScale adapter for type-safe MySQL queries
  • How to evaluate Supabase as the recommended alternative for new Bolt projects
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate18 min read25 minutesDatabaseApril 2026RapidDev Engineering Team
TL;DR

MySQL's mysql2 driver uses TCP protocol and fails in Bolt's WebContainer during development. Three options: (1) PlanetScale's @planetscale/database package uses HTTP and works in the WebContainer for development; (2) Deploy first, then mysql2 works normally in API routes; (3) Prisma ORM with PlanetScale adapter for type-safe queries. For new projects with no existing MySQL dependency, Supabase (PostgreSQL) is the recommended path of least resistance.

Connect Bolt.new to MySQL: HTTP Client for Dev, mysql2 After Deployment

MySQL is one of the most widely deployed databases in the world, powering everything from WordPress installations to large-scale e-commerce platforms. If you have an existing MySQL database, connecting a Bolt-built application to it requires one important piece of context before writing code: the standard Node.js MySQL driver (mysql2) uses TCP protocol to connect to MySQL's default port 3306. Bolt's WebContainer cannot open TCP connections — so mysql2 will fail during development in the Bolt editor with connection refused errors.

This TCP limitation is an architectural fact of browser-based runtimes, not a configuration problem. The good news is there are clear solutions depending on your situation and timeline. PlanetScale, a MySQL-compatible database service, provides an npm package (`@planetscale/database`) that uses HTTP instead of TCP. This package works perfectly in Bolt's WebContainer, letting you develop and test your MySQL API routes without deploying. If your database is on PlanetScale or you are willing to migrate, this is the smoothest development experience.

For existing MySQL databases (on AWS RDS, Google Cloud SQL, DigitalOcean, self-hosted), the recommended workflow is to deploy your Bolt app to Netlify or Bolt Cloud first — a 30-second process — and then build and test database-connected API routes from the deployed environment. Your React components and non-database routes can still be previewed in the WebContainer; only the routes that require MySQL need the deployed environment. Prisma ORM with the mysql2 engine is another strong option for teams that prefer type-safe queries with schema management, though it shares the same TCP limitation in the WebContainer.

Integration method

Bolt Chat + API Route

MySQL integration with Bolt requires a deployment-first approach for mysql2 since TCP connections are blocked in the WebContainer. PlanetScale's @planetscale/database package provides an HTTP-based MySQL-compatible client that works in the WebContainer for development. After deployment to Netlify or Bolt Cloud, standard mysql2 and Prisma with MySQL both work normally in API routes. Supabase (PostgreSQL) is the recommended alternative for new projects.

Prerequisites

  • A Bolt.new account with a Next.js project
  • A MySQL database — existing MySQL server, AWS RDS, Cloud SQL, or PlanetScale for WebContainer-compatible development
  • MySQL credentials (host, port, database, username, password)
  • For PlanetScale approach: a PlanetScale account and database at app.planetscale.com
  • For deploy-first approach: a Netlify account for hosting where mysql2 works normally

Step-by-step guide

1

Understand the TCP Limitation and Choose Your MySQL Path

The mysql2 driver connects to MySQL using TCP on port 3306 (or your configured port). Bolt's WebContainer cannot open TCP socket connections — this is a browser-based runtime constraint, not a network configuration issue. When you run a mysql2 connection in the WebContainer, you will see a connection refused error regardless of your MySQL server's firewall settings or accessibility. Three paths forward: Path 1 — PlanetScale HTTP client (best for WebContainer development): PlanetScale provides `@planetscale/database`, an npm package that connects to MySQL over HTTPS using the Fetch API. This works perfectly in the WebContainer. PlanetScale offers a free tier with one database. If you are starting a new project or willing to use PlanetScale as your MySQL provider, this gives you the best development experience — you can build and test database queries directly in the Bolt preview. Path 2 — Deploy first with mysql2 (best for existing MySQL databases): If you have an existing MySQL database you cannot migrate, deploy to Netlify in 30 seconds, add environment variables, and develop with the deployed environment as your database test environment. Your React components can still be iterated in the WebContainer; only routes that call `getPool()` need the deployed environment. Path 3 — Prisma with MySQL: For type-safe queries with schema management. Prisma uses mysql2 under the hood and shares the TCP limitation. Works after deployment. Good choice if your team prefers ORM patterns over raw SQL. For new projects with no MySQL dependency: use Bolt Database (built on Supabase/PostgreSQL) or a direct Supabase connection. Both use HTTP and work in the WebContainer with no deployment required for database features.

Bolt.new Prompt

Set up the MySQL configuration for this project. Create a lib/mysql.ts file with a detailed comment at the top explaining the WebContainer TCP limitation and the three integration paths (PlanetScale HTTP, deploy-first, Prisma). Export a getPool() function using mysql2/promise that creates a connection pool. Export a query() helper that accepts parameterized SQL and an optional params array. Add .env configuration with MYSQL_HOST, MYSQL_PORT (default 1433), MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, and MYSQL_SSL (default true).

Paste this in Bolt.new chat

lib/mysql.ts
1// lib/mysql.ts
2// ⚠️ MYSQL2 TCP LIMITATION IN BOLT WEBCONTAINER
3// mysql2 uses TCP protocol (port 3306) which is not supported in
4// Bolt's WebContainer browser-based runtime environment.
5//
6// DEVELOPMENT OPTIONS:
7// 1. PlanetScale: Use @planetscale/database (HTTP-based) — works in WebContainer
8// Install: npm install @planetscale/database
9// See lib/planetscale.ts for the HTTP-based alternative
10//
11// 2. Deploy First: Deploy to Netlify (30 seconds), add env vars,
12// test API routes at deployed URL where TCP works normally
13//
14// 3. New Project: Use Supabase (PostgreSQL) — Bolt's native database,
15// HTTP-based, zero configuration, works in WebContainer immediately
16
17import mysql from 'mysql2/promise';
18
19let pool: mysql.Pool | null = null;
20
21export function getPool(): mysql.Pool {
22 if (pool) return pool;
23
24 const host = process.env.MYSQL_HOST;
25 if (!host) {
26 throw new Error(
27 'MySQL is not configured. Set MYSQL_HOST, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD in .env. ' +
28 'Note: mysql2 requires TCP — it cannot connect from Bolt\'s WebContainer. Deploy to Netlify first or use PlanetScale\'s HTTP client.'
29 );
30 }
31
32 pool = mysql.createPool({
33 host,
34 port: parseInt(process.env.MYSQL_PORT || '3306', 10),
35 database: process.env.MYSQL_DATABASE,
36 user: process.env.MYSQL_USER,
37 password: process.env.MYSQL_PASSWORD,
38 ssl: process.env.MYSQL_SSL !== 'false' ? { rejectUnauthorized: true } : undefined,
39 waitForConnections: true,
40 connectionLimit: 10,
41 queueLimit: 0,
42 enableKeepAlive: true,
43 keepAliveInitialDelay: 0,
44 });
45
46 return pool;
47}
48
49export async function mysqlQuery<T = mysql.RowDataPacket[]>(
50 sql: string,
51 params?: unknown[]
52): Promise<T> {
53 const connection = getPool();
54 const [rows] = await connection.execute<mysql.RowDataPacket[]>(sql, params);
55 return rows as T;
56}

Pro tip: Use mysql2/promise (the promise-based variant) rather than the callback-based mysql2. The promise API works better with async/await in Next.js API routes and avoids the nested callback patterns of the original MySQL driver.

Expected result: The mysql.ts helper is in place with clear WebContainer limitation documentation. The pool configuration reads from environment variables. This file compiles correctly in the WebContainer but will only successfully connect after deployment.

2

PlanetScale HTTP Client for WebContainer Development

PlanetScale's `@planetscale/database` package uses the Fetch API to communicate with PlanetScale's HTTP gateway rather than opening a TCP socket. This makes it fully compatible with Bolt's WebContainer for development. PlanetScale is MySQL-compatible — it speaks MySQL syntax and supports most MySQL features, with the notable exception that foreign key constraints are not enforced (PlanetScale uses its own schema migration system instead). To get started with PlanetScale, go to app.planetscale.com and create an account. Create a new database and a branch (start with the `main` branch). In the database dashboard, go to 'Passwords' and create a new password for your branch. This gives you a hostname, username, and password that go in your .env file. Install the package from the Bolt terminal: `npm install @planetscale/database`. Create a client with `new Client({ host, username, password })`. Execute queries using `client.execute(sql, params)`. The execute method returns an object with `rows` (array of row objects), `fields` (column metadata), `rowsAffected`, and `insertId`. Unlike mysql2 which uses `?` as placeholders, PlanetScale's HTTP client also supports `?` in the same position syntax. For schema management, PlanetScale uses its own deploy requests system rather than raw migrations. Create your tables using PlanetScale's web console, or use the PlanetScale CLI. For development, you can also create tables by running CREATE TABLE statements through the execute function. PlanetScale's free tier includes one database, 5GB storage, and 1 billion row reads per month — significantly generous for development and small production apps. Paid plans start at $39/month for production workloads.

Bolt.new Prompt

Install @planetscale/database. Create lib/planetscale.ts with a PlanetScale client using PLANETSCALE_HOST, PLANETSCALE_USERNAME, PLANETSCALE_PASSWORD from process.env. Export a psQuery helper that executes SQL with params and returns typed rows. Add a comment that this works in Bolt's WebContainer because it uses HTTP instead of TCP. Create a Next.js API route at /api/planetscale/products that queries a products table with search and pagination using the psQuery helper.

Paste this in Bolt.new chat

lib/planetscale.ts
1// lib/planetscale.ts
2// PlanetScale uses HTTP (not TCP) — works in Bolt's WebContainer during development
3import { Client } from '@planetscale/database';
4
5let client: Client | null = null;
6
7export function getPlanetScaleClient(): Client {
8 if (client) return client;
9
10 const host = process.env.PLANETSCALE_HOST;
11 const username = process.env.PLANETSCALE_USERNAME;
12 const password = process.env.PLANETSCALE_PASSWORD;
13
14 if (!host || !username || !password) {
15 throw new Error(
16 'PlanetScale credentials not configured. Set PLANETSCALE_HOST, PLANETSCALE_USERNAME, PLANETSCALE_PASSWORD in .env. ' +
17 'Create credentials at app.planetscale.com → Database → Passwords.'
18 );
19 }
20
21 client = new Client({ host, username, password });
22 return client;
23}
24
25export async function psQuery<T = Record<string, unknown>>(
26 sql: string,
27 params?: unknown[]
28): Promise<T[]> {
29 const db = getPlanetScaleClient();
30 const result = await db.execute(sql, params);
31 return result.rows as T[];
32}
33
34export async function psQueryOne<T = Record<string, unknown>>(
35 sql: string,
36 params?: unknown[]
37): Promise<T | null> {
38 const rows = await psQuery<T>(sql, params);
39 return rows[0] || null;
40}
41
42export async function psExecute(
43 sql: string,
44 params?: unknown[]
45): Promise<{ rowsAffected: number; insertId: string | null }> {
46 const db = getPlanetScaleClient();
47 const result = await db.execute(sql, params);
48 return {
49 rowsAffected: result.rowsAffected,
50 insertId: result.insertId || null,
51 };
52}

Pro tip: PlanetScale does not enforce foreign key constraints. Instead, it uses a branching and deploy request model for schema changes. This means you can run ALTER TABLE statements directly in development branches without the risk of FK constraint violations blocking migrations.

Expected result: The PlanetScale client connects successfully from Bolt's WebContainer. A test query like psQuery('SELECT 1 as test') returns [{ test: 1 }] without any TCP connection errors. This confirms HTTP-based MySQL connectivity works in the preview.

3

Build MySQL API Routes with Connection Pooling

Whether you use mysql2 (deployed) or PlanetScale's HTTP client (WebContainer), the API route patterns are identical. Create table-specific routes with parameterized queries, error handling, and pagination. For mysql2 in deployed environments, connection pooling is critical. The `getPool()` function creates the pool once and reuses it across requests. In serverless environments (Netlify Functions, Bolt Cloud), each function invocation may run in the same process for warm starts or spin up fresh for cold starts. The module-level pool variable handles both cases: warm starts reuse the pool, cold starts create a new one. Parameterized queries use `?` placeholders in mysql2: `SELECT * FROM products WHERE category = ? AND price < ?` with `[category, maxPrice]` as the second argument. This escapes values automatically and prevents SQL injection. Never concatenate user-provided values directly into SQL strings. For pagination, use `LIMIT` and `OFFSET`: `SELECT * FROM products LIMIT ? OFFSET ?`. For large tables, `OFFSET` becomes inefficient at high page numbers — keyset pagination (WHERE id > lastSeenId) is more performant but requires more complex query logic. For most dashboards with hundreds to thousands of records, `OFFSET` pagination is fine. Error handling should distinguish between connection errors (database unreachable) and query errors (SQL syntax, constraint violations, missing columns). mysql2 throws `Error` objects with a `code` property for database-level errors — common codes include `ER_DUP_ENTRY` (duplicate key), `ER_NO_SUCH_TABLE` (table doesn't exist), and `ER_ACCESS_DENIED_ERROR` (wrong credentials).

Bolt.new Prompt

Create a Next.js API route at app/api/mysql/records/route.ts that works with both the PlanetScale HTTP client (for development) and mysql2 (for deployment). Accept query params: table, search, page, pageSize. Build parameterized SELECT query. Return { records, total, page, pageSize }. Include a switchable flag that uses psQuery from lib/planetscale.ts when PLANETSCALE_HOST is set, or mysqlQuery from lib/mysql.ts when MYSQL_HOST is set. Add error handling that distinguishes TCP errors from SQL errors.

Paste this in Bolt.new chat

app/api/mysql/records/route.ts
1// app/api/mysql/records/route.ts
2import { NextResponse } from 'next/server';
3
4// Use PlanetScale HTTP client if configured (works in WebContainer)
5// Fall back to mysql2 if MYSQL_HOST is configured (requires deployment)
6async function executeQuery<T>(sql: string, params: unknown[]): Promise<T[]> {
7 if (process.env.PLANETSCALE_HOST) {
8 const { psQuery } = await import('@/lib/planetscale');
9 return psQuery<T>(sql, params);
10 } else if (process.env.MYSQL_HOST) {
11 const { mysqlQuery } = await import('@/lib/mysql');
12 return mysqlQuery<T>(sql, params);
13 } else {
14 throw new Error(
15 'No database configured. Set either PLANETSCALE_HOST (HTTP, works in WebContainer) ' +
16 'or MYSQL_HOST (TCP, requires deployment) in .env'
17 );
18 }
19}
20
21export async function GET(request: Request) {
22 const { searchParams } = new URL(request.url);
23 const table = searchParams.get('table');
24 const search = searchParams.get('search') || '';
25 const page = parseInt(searchParams.get('page') || '1', 10);
26 const pageSize = Math.min(parseInt(searchParams.get('pageSize') || '20', 10), 100);
27 const offset = (page - 1) * pageSize;
28
29 if (!table || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
30 return NextResponse.json({ error: 'Invalid table name' }, { status: 400 });
31 }
32
33 try {
34 // Table names cannot be parameterized — validate via regex above
35 const searchClause = search ? 'WHERE name LIKE ?' : '';
36 const searchParam = search ? [`%${search}%`] : [];
37
38 const [records, countResult] = await Promise.all([
39 executeQuery<Record<string, unknown>>(
40 `SELECT * FROM \`${table}\` ${searchClause} LIMIT ? OFFSET ?`,
41 [...searchParam, pageSize, offset]
42 ),
43 executeQuery<[{ total: number }]>(
44 `SELECT COUNT(*) AS total FROM \`${table}\` ${searchClause}`,
45 searchParam
46 ),
47 ]);
48
49 return NextResponse.json({
50 records,
51 total: (countResult as unknown as Array<{ total: number }>)[0]?.total || 0,
52 page,
53 pageSize,
54 });
55 } catch (err) {
56 const message = err instanceof Error ? err.message : 'Query failed';
57 const isTcpError = message.includes('ECONNREFUSED') || message.includes('Failed to connect');
58 return NextResponse.json(
59 {
60 error: message,
61 hint: isTcpError
62 ? 'mysql2 uses TCP which is blocked in Bolt WebContainer. Use PlanetScale HTTP client or deploy first.'
63 : undefined,
64 },
65 { status: 500 }
66 );
67 }
68}

Pro tip: Never use user-provided table names directly in SQL even with parameterization — mysql2 cannot parameterize table names, only values. Validate table names against a strict regex or an allowlist of known table names before including them in the query string.

Expected result: When PLANETSCALE_HOST is configured, the route returns data from PlanetScale in the WebContainer preview. When MYSQL_HOST is configured, the route works in deployed environments. The automatic selection between providers makes the transition transparent.

4

Set Up Prisma ORM with MySQL (Post-Deployment)

Prisma ORM provides type-safe database queries, schema management with migrations, and a clean client API. For MySQL, Prisma uses mysql2 under the hood and shares the same TCP limitation — it requires a deployed environment for database connectivity. However, Prisma adds significant value through type safety, auto-generated TypeScript types, and the migration system. Initialize Prisma by running `npx prisma init --datasource-provider mysql` in the Bolt terminal. This creates a `prisma/schema.prisma` file and adds `DATABASE_URL` to .env. The DATABASE_URL format for MySQL is: `mysql://user:password@host:port/database`. For PlanetScale, Prisma provides a specific adapter: `provider = 'mysql'` with `planetscale` as the relation mode setting, and use `@planetscale/database` as the driver adapter. Define your models in schema.prisma. Each model maps to a database table. Prisma generates TypeScript types for all models automatically. Run `npx prisma generate` to regenerate the client after schema changes. Run `npx prisma db push` to push schema changes to the database (good for prototyping) or `npx prisma migrate dev` for production-grade migrations. For the Next.js integration, create a `lib/prisma.ts` singleton that prevents multiple Prisma client instances during development hot reload. The `global as any` pattern is a Next.js best practice for this — it persists the client across hot reloads without creating new connections on each reload. Prisma's query API is significantly more ergonomic than raw SQL for complex queries with relations. `prisma.user.findMany({ include: { orders: { include: { products: true } } } })` joins three tables in one type-safe call without writing JOIN syntax.

Bolt.new Prompt

Set up Prisma with MySQL for this project. Run npx prisma init with mysql provider. Create prisma/schema.prisma with a User model (id Int, email String unique, name String, createdAt DateTime) and Product model (id Int, name String, price Float, category String, stock Int). Create lib/prisma.ts with the Next.js singleton pattern. Create /api/prisma/users route using Prisma client with findMany, search by name, pagination, and include count of related orders. Add a comment explaining Prisma MySQL requires deployment due to TCP.

Paste this in Bolt.new chat

lib/prisma.ts
1// lib/prisma.ts
2// Note: Prisma with MySQL uses mysql2 (TCP) and requires deployment to connect.
3// For WebContainer development, configure DATABASE_URL to use PlanetScale with
4// the Prisma PlanetScale adapter, or use the psQuery helper from lib/planetscale.ts
5import { PrismaClient } from '@prisma/client';
6
7const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
8
9export const prisma =
10 globalForPrisma.prisma ??
11 new PrismaClient({
12 log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
13 });
14
15if (process.env.NODE_ENV !== 'production') {
16 globalForPrisma.prisma = prisma;
17}
18
19// prisma/schema.prisma:
20// generator client {
21// provider = "prisma-client-js"
22// }
23// datasource db {
24// provider = "mysql"
25// url = env("DATABASE_URL")
26// relationMode = "prisma" // Use this for PlanetScale (disables FK constraints)
27// }
28// model User {
29// id Int @id @default(autoincrement())
30// email String @unique
31// name String
32// createdAt DateTime @default(now())
33// }
34
35// .env DATABASE_URL format:
36// DATABASE_URL="mysql://user:password@host:3306/database?ssl={rejectUnauthorized:true}"
37// For PlanetScale:
38// DATABASE_URL="mysql://username:password@host.us-east.psdb.cloud/database?sslaccept=strict"

Pro tip: When using Prisma with PlanetScale, set relationMode = 'prisma' in the datasource block. This tells Prisma to handle relations in application code rather than with database-level foreign keys (which PlanetScale does not support). Without this, Prisma migration commands will fail.

Expected result: Prisma is configured with the MySQL schema. Running npx prisma generate creates the TypeScript client. The lib/prisma.ts singleton is ready for use in API routes after deployment where TCP connections work.

Common use cases

Read Existing MySQL Database via API Routes

Connect a Bolt app to an existing MySQL database — on RDS, Cloud SQL, or a VPS — to build a modern UI on top of legacy data. Expose MySQL tables through typed Next.js API routes and display the data in React components. The deploy-first pattern means your app is live while you iterate on the UI.

Bolt.new Prompt

Build Next.js API routes that connect to a MySQL database. Create a lib/mysql.ts file with a getPool() function that creates a mysql2 connection pool using MYSQL_HOST, MYSQL_PORT, MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD from process.env with ssl: { rejectUnauthorized: true }. Export a query() helper that executes parameterized SQL. Create /api/mysql/products route that fetches products with search and pagination. Add a comment explaining mysql2 requires TCP and needs a deployed environment to connect.

Copy this prompt to try it in Bolt.new

PlanetScale as WebContainer-Compatible MySQL

Use PlanetScale's MySQL-compatible database with its HTTP-based @planetscale/database client for full development-to-production MySQL functionality without the WebContainer TCP limitation. PlanetScale's free tier gives you one database, making it cost-free for development and small projects.

Bolt.new Prompt

Set up PlanetScale database connection for this Bolt project. Install @planetscale/database. Create a lib/planetscale.ts file with a ps client using PLANETSCALE_HOST, PLANETSCALE_USERNAME, and PLANETSCALE_PASSWORD from process.env. Export a psFetch helper that executes SQL queries and returns typed results. Create /api/data/users route that queries a users table with pagination and search. This approach works in Bolt's WebContainer preview without deploying.

Copy this prompt to try it in Bolt.new

Prisma ORM with MySQL (Post-Deployment)

Use Prisma ORM with MySQL for type-safe, migration-managed database access. Define your data model in Prisma schema, generate TypeScript client code, and use it in API routes for safe queries without writing raw SQL. Prisma works after deployment where TCP connections are available.

Bolt.new Prompt

Set up Prisma ORM with MySQL in this Next.js project. Create prisma/schema.prisma with datasource db using mysql provider and DATABASE_URL env var. Add models for User, Product, and Order tables. Run npx prisma generate. Create lib/prisma.ts singleton. Build /api/users, /api/products, and /api/orders routes using Prisma client with proper relation includes. Note that Prisma MySQL requires a deployed environment. Create a .env.example with DATABASE_URL format for MySQL.

Copy this prompt to try it in Bolt.new

Troubleshooting

Error: connect ECONNREFUSED 127.0.0.1:3306 in the WebContainer

Cause: mysql2 is attempting a TCP connection to MySQL on port 3306, which is blocked in Bolt's WebContainer runtime. This is the expected behavior — mysql2 cannot connect from a browser-based environment.

Solution: This is not a configuration error. Switch to PlanetScale's @planetscale/database HTTP client for WebContainer development, or deploy to Netlify/Bolt Cloud where TCP connections work. The error disappears in the deployed environment.

typescript
1// Alternative that works in WebContainer:
2// npm install @planetscale/database
3import { Client } from '@planetscale/database';
4const client = new Client({
5 host: process.env.PLANETSCALE_HOST,
6 username: process.env.PLANETSCALE_USERNAME,
7 password: process.env.PLANETSCALE_PASSWORD,
8});
9const result = await client.execute('SELECT 1 AS test');

PlanetScale query returns empty rows for a table that has data

Cause: The query is running against the wrong branch. PlanetScale uses database branches — the credentials (username/password) are branch-specific. If you created credentials for the 'main' branch but the table was created on a 'dev' branch, you won't see the data.

Solution: Verify the PlanetScale branch associated with your credentials in app.planetscale.com → Database → Passwords. The password name shows which branch it connects to. Create a separate password for each branch you need to connect to.

Prisma error: P1001 Can't reach database server, or P1017 Server has closed the connection

Cause: Prisma is attempting a TCP connection from the WebContainer (same limitation as mysql2), or the DATABASE_URL connection string is malformed.

Solution: For WebContainer development, use the PlanetScale HTTP client instead of Prisma MySQL. For deployed environments, verify the DATABASE_URL format is exactly: mysql://user:password@host:3306/database. Test connectivity by deploying and checking the health route.

typescript
1// Correct MySQL DATABASE_URL format:
2// DATABASE_URL="mysql://myuser:mypassword@myhost.example.com:3306/mydatabase"
3// For PlanetScale:
4// DATABASE_URL="mysql://pscale_user:pscale_pass@aws.connect.psdb.cloud/mydb?sslaccept=strict"

ER_ACCESS_DENIED_ERROR: Access denied for user after deployment

Cause: The MySQL user credentials in MYSQL_USER and MYSQL_PASSWORD do not match the MySQL server's user configuration, or the user lacks permission to access the specified database.

Solution: Verify credentials by connecting with a MySQL client. Ensure the user has been granted access to the database: GRANT ALL PRIVILEGES ON database_name.* TO 'user'@'%'; FLUSH PRIVILEGES; The '%' wildcard allows connections from any host, which is necessary for cloud-hosted serverless functions.

typescript
1-- Run in MySQL to grant access from any host:
2GRANT SELECT, INSERT, UPDATE, DELETE ON your_database.* TO 'app_user'@'%';
3FLUSH PRIVILEGES;
4-- For serverless, '@%' is typically needed since the IP changes between invocations

Best practices

  • Use PlanetScale's @planetscale/database HTTP client during development in Bolt's WebContainer — it provides the same MySQL syntax with HTTP connectivity that works without deploying.
  • Never store MySQL credentials (MYSQL_PASSWORD, PLANETSCALE_PASSWORD) in environment variables with the NEXT_PUBLIC_ prefix — these must remain server-side to prevent exposure in client JavaScript.
  • Use parameterized queries exclusively for all user-provided values — never use string interpolation or concatenation for SQL query values, and validate table names with a strict allowlist before including them in queries.
  • Configure MySQL connection pools with a reasonable maximum (10 connections) and enable keepAlive to reduce reconnection overhead in serverless environments where the pool may be reused across function invocations.
  • Enable SSL/TLS for all MySQL connections in production — cloud databases (RDS, Cloud SQL, PlanetScale) require it and self-hosted MySQL can be configured to require it. Use rejectUnauthorized: true to validate the server certificate.
  • For Prisma with PlanetScale, set relationMode: 'prisma' in schema.prisma — PlanetScale does not support foreign key constraints at the database level, so Prisma must handle relation integrity in application code.
  • Add a /api/health or /api/db-check route that tests database connectivity and returns clear status information — this is invaluable for quickly diagnosing connection issues after deployment.
  • For new projects without an existing MySQL database requirement, use Bolt Database (Supabase/PostgreSQL) — it works natively in the WebContainer with zero configuration and eliminates the TCP limitation entirely.

Alternatives

Frequently asked questions

Why does mysql2 fail in Bolt's WebContainer even with correct credentials?

mysql2 uses the MySQL wire protocol (TDS variant) over TCP port 3306. Bolt's WebContainer is a WebAssembly-based Node.js environment running inside a browser tab, and browsers cannot make raw TCP socket connections. This is an architectural constraint, not a configuration error — no amount of credential changes or firewall rules will make mysql2 connect in the WebContainer.

Does PlanetScale's @planetscale/database package support all MySQL features?

PlanetScale supports most MySQL 8.0 features including all SQL queries, indexes, triggers, and stored procedures. The main difference is that PlanetScale does not enforce foreign key constraints at the database level (it uses application-level relation handling). PlanetScale also uses a branching model for schema changes rather than direct DDL statements in production.

Can I use an existing MySQL database (not PlanetScale) during Bolt development?

Not directly in the WebContainer preview — you will need to deploy first. The deploy-first workflow is: build your API routes in Bolt (they compile fine even without a database connection), deploy to Netlify in 30 seconds, add MYSQL_HOST and other credentials as environment variables, and test your database routes via the deployed URL. React components still iterate freely in the preview.

Is Prisma better than raw mysql2 for Bolt projects?

Prisma provides better TypeScript types, auto-generated query helpers, and migration management, which are significant advantages for team projects. For simple CRUD applications, raw mysql2 with parameterized queries is simpler to set up. Both share the same WebContainer limitation. Choose Prisma for projects with complex data models and multiple developers; choose raw mysql2 for straightforward integrations with existing databases.

What is the easiest MySQL-compatible database for a new Bolt project?

PlanetScale is the easiest MySQL-compatible option for Bolt — free tier, HTTP-based client that works in the WebContainer, and a clean branching model for schema changes. However, if you have no strong reason to use MySQL specifically, Supabase (PostgreSQL) is even easier as Bolt's native database with zero configuration and full WebContainer support.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.