---
title: MySQL
description: Give Bapx agents and workflow runs durable, shared state with MySQL 8 and InnoDB.
package:
  name: '@bapX/mysql'
  href: https://www.npmjs.com/package/@bapX/mysql
---

## Quickstart

Add durable, shared MySQL persistence to an existing Bapx project with the [MySQL](https://www.mysql.com) blueprint. Run the following command in your terminal or coding agent of choice:

```sh
bapX add database mysql
```

## Overview

The MySQL blueprint installs `@bapX/mysql` and `mysql2` and creates a source-root `db.ts`. The generated adapter uses a pool for ordinary queries and keeps each transaction on one checked-out connection:

```ts title="src/db.ts (abridged)"
import { mysql, type MysqlQuery } from '@bapX/mysql';
import mysql2 from 'mysql2/promise';

const pool = mysql2.createPool(process.env.MYSQL_URL!);

const toRows = (result: unknown): Record<string, unknown>[] =>
  Array.isArray(result) ? result.map((row) => ({ ...row })) : [];

export default mysql({
  query: async (text, params = []) => {
    const [result] = await pool.execute(text, params);
    return toRows(result);
  },
  transaction: async <T>(fn: (tx: { query: MysqlQuery }) => Promise<T>) => {
    const connection = await pool.getConnection();
    await connection.beginTransaction();
    // ...
  },
  close: () => pool.end(),
});
```

Bapx discovers the adapter at build time and wires it into the generated Node server. On startup, it creates and verifies the required MySQL 8 InnoDB tables. Canonical agent conversations, immutable attachments, accepted submissions, and workflow history then survive process replacement. Replicas may share durable state and workflow history, but each agent instance still requires one live Node owner. Application business data remains application-owned. The blueprint applies only to Node targets because Cloudflare deployments use Durable Object SQLite instead.

## Configure

| Variable    | Purpose                                                                     |
| ----------- | --------------------------------------------------------------------------- |
| `MYSQL_URL` | **Required** — MySQL connection string, supplied by your database provider. |

The driver reads this value at runtime. Supply it through your platform's
secret store, never commit it, and configure `mysql2` TLS options when your
provider requires them. For local development, `bapX dev --env <file>` and
`bapX run --env <file>` load any `.env`-format file.

The blueprint installs `@bapX/mysql` and `mysql2`, then writes a source-root
`db.ts`. Bapx discovers that file at build time and wires it into the generated
Node server.

`@bapX/mysql` supports **MySQL 8 with InnoDB** on the **Node.js target**. The
Cloudflare target uses Durable Object SQLite automatically and rejects `db.ts`
at build time. See [Database](/guide/database/) for persistence by target.

## Bring your own driver

`@bapX/mysql` does not bundle a production driver. It accepts a runner so your
application owns pooling, TLS, credentials, and connection lifecycle. The
canonical `mysql2` runner uses `pool.execute()` for normal queries and one
checked-out connection for each callback transaction:

```ts title="src/db.ts"
import { mysql, type MysqlQuery } from '@bapX/mysql';
import mysql2 from 'mysql2/promise';

const pool = mysql2.createPool(process.env.MYSQL_URL!);

const toRows = (result: unknown): Record<string, unknown>[] =>
  Array.isArray(result) ? result.map((row) => ({ ...row })) : [];

export default mysql({
  query: async (text, params = []) => {
    const [result] = await pool.execute(text, params);
    return toRows(result);
  },
  transaction: async <T>(fn: (tx: { query: MysqlQuery }) => Promise<T>) => {
    const connection = await pool.getConnection();
    try {
      await connection.beginTransaction();
      const result = await fn({
        query: async (text, params = []) => {
          const [rows] = await connection.execute(text, params);
          return toRows(rows);
        },
      });
      await connection.commit();
      return result;
    } catch (error) {
      await connection.rollback();
      throw error;
    } finally {
      connection.release();
    }
  },
  close: () => pool.end(),
});
```

The runner uses `?` placeholders and returns plain row objects. Every query in a
transaction callback must use the checked-out connection; issuing those calls
through the pool could move work onto another connection and outside the
transaction.

## Migrations

The adapter's `migrate()` hook runs automatically when the generated Node
server starts. It creates Bapx's `bapX_*` tables idempotently, verifies the
complete schema, and then stamps its version. All transactional tables use
InnoDB. There is no separate migration command, and a database written by a
newer Bapx version refuses to start rather than risking incompatible writes.

## What gets stored

A Bapx database stores runtime state, not your whole application.

| Stored by Bapx                                                   | Not stored by Bapx                       |
| ---------------------------------------------------------------- | ---------------------------------------- |
| Canonical agent conversation streams and compaction records       | Sandbox files and installed dependencies |
| Immutable attachment payloads                                    | External API side effects                |
| Accepted direct prompts and `dispatch(...)` submissions          | Application-owned business data          |
| Durable submission claims and leases                              | Provider credentials or secrets          |
| Workflow-run records, persisted events, and run indexes           |                                          |

See [Durable Agents](/concepts/durable-execution/) for recovery behavior
and the [Data Persistence API](/api/data-persistence-api/) for the adapter
contract.

## When to choose MySQL

Choose MySQL when your Node deployment already operates MySQL 8, or when replacement processes and multiple replicas need durable agent state and shared workflow history in an InnoDB-backed database. Preserve one live owner per agent instance. For single-host persistence, file-backed `sqlite()` may
be simpler. Choose [`@bapX/postgres`](/ecosystem/databases/postgres/) when
Postgres is your existing operational standard, or
[`@bapX/libsql`](/ecosystem/databases/libsql/) for SQLite and libSQL
workloads.
