PlanetScale Database JavaScript Driver

repository·main·Indexed 22 days ago

https://github.com/planetscale/database-js

A Fetch API-compatible Vitess/MySQL database driver designed for serverless and edge compute platforms such as Cloudflare Workers and Vercel Edge Functions. It provides a Client connection factory, transaction management, and support for both positional and named query parameters. The driver allows for custom fetch implementations, type casting, and flexible row return formats.

Tokens
3.8K
Snippets
16
Records
17
Agent score
77%

What's inside @planetscale/database

  1. Provide a custom fetch function

    main

    If you are using a version of Node.js older than 18 (which lacks a global fetch), you can provide a custom fetch implementation in the configuration object. For HTTP/2 support, you can use the fetch-h2 shim.

    import { connect } from '@planetscale/database'
    import { fetch } from 'undici'
    
    const config = {
      fetch,
      host: '<host>',
      username: '<user>',
      password: '<password>'
    }
    
    const conn = connect(config)
    const results = await conn.execute('select 1 from dual')
    console.log(results)
  2. Configure connection using a Database URL

    main

    Instead of providing individual credentials, you can provide a single url property in the configuration object. This supports the mysql://user:pass@host format.

    import { connect } from '@planetscale/database'
    
    const config = {
      url: process.env['DATABASE_URL'] || 'mysql://user:pass@host'
    }
    
    const conn = connect(config)
  3. Install @planetscale/database via npm

    main

    Install the PlanetScale serverless JavaScript driver using npm to connect to Vitess/MySQL databases from serverless or edge environments (e.g., Cloudflare Workers, Vercel Edge Functions).

    npm install @planetscale/database
  4. Initialize a connection with Client or connect()

    main

    To interact with PlanetScale, you can either instantiate the Client class or use the connect() function. Both require a Config object. The Client provides a high-level interface including transaction management, while connect() returns a Connection object for direct query execution.

    Configuration Options

    • url: A full database connection string (e.g., mysql://user:pass@host/db). If provided, username, password, and host are automatically extracted.
    • username: Database username.
    • password: Database password.
    • host: Database host.
    • fetch: A custom fetch implementation (useful for specific environments like Cloudflare Workers).
    • format: A custom query parameter format function.
    • cast: A custom type casting function.
    import { Client, connect } from '@planetscale/database';
    
    // Using Client
    const client = new Client({ url: 'mysql://user:pass@host/db' });
    
    // Using connect()
    const connection = connect({ host: 'host', username: 'user', password: 'pass' });
  5. Basic usage with connect()

    main

    Use the connect function to establish a connection by providing a configuration object containing host, username, and password. You can then use conn.execute() to run SQL queries.

    import { connect } from '@planetscale/database'
    
    const config = {
      host: '<host>',
      username: '<user>',
      password: '<password>'
    }
    
    const conn = connect(config)
    const results = await conn.execute('select 1 from dual where 1=?', [1])
    console.log(results)
  6. Customize type casting

    main

    You can control how column values are converted to JavaScript types by providing a cast function in the configuration. You can also override this behavior on a per-query basis by passing a cast function as the third argument to execute.

    import { connect, cast } from '@planetscale/database'
    
    function inflate(field, value) {
      if (field.type === 'INT64' || field.type === 'UINT64') {
        return BigInt(value)
      }
      return cast(field, value)
    }
    
    const config = {
      cast: inflate,
      host: '<host>',
      username: '<user>',
      password: '<password>'
    }
    
    const conn = connect(config)
    
    // Per-query override
    const result = await conn.execute(
      'SELECT userId, SUM(balance) AS balance FROM UserBalanceItem GROUP BY userId',
      {},
      {
        cast: (field, value) => {
          if (field.name === 'balance') {
            return BigInt(value)
          }
          return cast(field, value)
        }
      }
    )
  7. Change row return formats with the `as` option

    main

    When calling execute, you can specify how rows are returned using the as option. Supported values are 'object' (returns an array of objects) and 'array' (returns an array of arrays).

    const query = 'select 1 as one, 2 as two where 1=?'
    
    // Returns objects: [{one: '1', two: '2'}]
    const objects = await conn.execute(query, [1], { as: 'object' })
    
    // Returns arrays: [['1', '2']]
    const arrays = await conn.execute(query, [1], { as: 'array' })
  8. Perform database transactions

    main

    Use the transaction method to execute multiple queries safely. If any unhandled error is thrown within the provided async callback, the transaction will automatically roll back.

    import { connect } from '@planetscale/database'
    
    const config = {
      host: '<host>',
      username: '<user>',
      password: '<password>'
    }
    
    const conn = connect(config)
    const results = await conn.transaction(async (tx) => {
      const whenBranch = await tx.execute('INSERT INTO branches (database_id, name) VALUES (?, ?)', [42, "planetscale"])
      const whenCounter = await tx.execute('INSERT INTO slotted_counters(record_type, record_id, slot, count) VALUES (?, ?, RAND() * 100, 1) ON DUPLICATE KEY UPDATE count = count + 1', ['branch_count', 42])
      return [whenBranch, whenCounter]
    })
    console.log(results)
  9. Use the Client connection factory

    main

    The Client class can be used as a factory to create fresh connections for each transaction or web request handler. Call client.connection() to obtain a connection instance.

    import { Client } from '@planetscale/database'
    
    const client = new Client({
      host: '<host>',
      username: '<user>',
      password: '<password>'
    })
    
    const conn = client.connection()
    const results = await conn.execute('select 1 from dual')
    console.log(results)
  10. Use custom query parameter formatting

    main

    The driver supports positional parameters (?) and named parameters (prefixed with :). You can override the default escaping behavior by providing a format function in the configuration object (e.g., using sqlstring).

    import { connect } from '@planetscale/database'
    import SqlString from 'sqlstring'
    
    const config = {
      format: SqlString.format,
      host: '<host>',
      username: '<user>',
      password: '<password>'
    }
    
    const conn = connect(config)
    const results = await conn.execute('select 1 from dual where 1=?', [42])
    console.log(results)
  11. Execute queries with execute()

    main

    The execute method runs a SQL query. It supports both object-based and array-based row returns via the as option.

    Parameters

    • query: The SQL string.
    • args: Query parameters (can be an object for named parameters or an array for positional parameters).
    • options:
      • as: Either 'object' (default) or 'array'. Determines the shape of the rows in the result.
      • cast: A custom casting function.

    Return Value

    Returns an ExecutedQuery object containing:

    • rows: The result rows (shaped by as).
    • fields: An array of Field objects describing the columns.
    • rowsAffected: Number of rows modified (for INSERT/UPDATE/DELETE).
    • insertId: The ID generated by the last insert.
    • headers: Column names.
    • types: A mapping of column names to their types.
    • statement: The final SQL string after parameter formatting.
    • time: Execution time in milliseconds.
    // Returns rows as objects: [{ id: 1, name: 'foo' }]
    const result = await client.execute('SELECT * FROM users WHERE id = ?', [1]);
    
    // Returns rows as arrays: [[1, 'foo']]
    const resultArray = await client.execute('SELECT * FROM users', null, { as: 'array' });
  12. Manage transactions with transaction()

    main

    Transactions ensure that a series of queries are executed atomically. You can use the transaction method on a Client or Connection instance. It automatically handles BEGIN, COMMIT, and ROLLBACK (if an error occurs).

    Pass a callback function to transaction that receives a Transaction object. Use this object to execute queries within the transaction scope.

    await client.transaction(async (tx) => {
      await tx.execute('UPDATE accounts SET balance = balance - 10 WHERE id = 1');
      await tx.execute('UPDATE accounts SET balance = balance + 10 WHERE id = 2');
    });