mysql2-extended
TypeScript icon, indicating that this package has built-in type declarations

1.4.0 • Public • Published

MySQL2 Extended

Thin convenience extension for the mysql2 library.

You may be interested in this if:

  • You want something more than the raw query function provided by mysql2.
  • You don't want a full-blown ORM.
  • You like hand-crafted SQL.
  • You want typed database results.

Coverage Status Build Status

Installation:

  • npm install mysql2-extended mysql2
  • yarn add mysql2-extended mysql2

Usage

Setup

import { MySQL2Extended } from 'mysql2-extended';
import { createPool } from 'mysql2/promise';

const pool = createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test',
});

const db = new MySQL2Extended(pool);

Typings

Types can be passed to each query function in order to get typed data back without casting. Note that the types are not validated against the actual data, but just a convenient way for the developer to add types.

This package provides generic types to help with common workflows. For example, you probably want types for your tables, like this:

type User = {
    id: number;                  // Auto increment in MySQL
    email: string;
    firstname: string | null;
    lastname: string | null;
    active: boolean;             // Default 'true' in MySQL column definition
};

When creating (inserting) a new user, you don't want to be forced to provide values for columns that have default values in the database. You can use the OptionalDefaultFields utility type:

type UserDefaultFields = 'id' | 'firstname' | 'lastname' | 'active';

// CreateUser will only enforce 'email' as required
type CreateUser = OptionalDefaultFields<User, UserDefaultFields>;

Querying

Note that all supplied parameters will use parameter binding in the underlying database driver.

Insert

Single
await db.insert<CreateUser>('users', {
    email: 'bob@test.com',
    firstname: 'Bob',
    active: true,
});
INSERT INTO `users` (`email`, `firstname`, `active`) VALUES ('bob@test.com', 'Bob', 1);
Bulk
await db.insert<CreateUser>('users', [
    { email: 'bob@test.com' },
    { email: 'another@test.com' },
]);
INSERT INTO `users` (`email`) VALUES ('bob@test.com'), ('another@test.com');

Select

Specific columns
const users = await db.select<Pick<User, 'firstname' | 'lastname'>>(
    ['firstname', 'lastname'],
    'users',
);
SELECT `firstname`, `lastname` FROM `users`;
All columns

First column argument used above is optional. If no columns are specified, all columns will be selected:

const users = await db.select<User>('users');
SELECT * FROM `users`;
WHERE conditions

Conditions provided in the object will be combined with AND. For more complex use-cases, see Raw for now.

const users = await db.select<User>('users', { id: 1, active: true });
SELECT * FROM `users` WHERE `id` = 1 AND `active` = 1;
Limit and offset
const users = await db.select<User>(
  'users',
  { id: 1, active: true }, // undefined/null if no conditions are present
  { limit: 10, offset: 5 },
);
SELECT * FROM `users` WHERE `id` = 1 AND `active` = 1 LIMIT 5, 10;
Ordering
const users = await db.select<User>(
  'users',
  { id: 1, active: 1 },
  {
    limit: 10,
    offset: 5,
    order: ['id', 'desc'],
    // order: [['firstname', 'desc'], ['lastname', 'asc']],
  },
);
SELECT * FROM `users`
WHERE `id` = 1 AND `active` = 1
ORDER BY `id` DESC
LIMIT 5, 10;

Update

All
await db.update<User>('users', { active: true })
UPDATE `users` SET `active` = 1;
WHERE conditions
await db.update('users', { firstname: 'Bob' }, { id: 1 }, { limit: 1 });
UPDATE `users` SET `firstname` = 'Bob' WHERE `id` = 1 LIMIT 1;

Delete

All
await db.delete('users');
DELETE FROM `users`;
WHERE conditions
await db.delete<User>('users', { id: 1, active: true });
DELETE FROM `users` WHERE `id` = 1 AND `active` = 1;

Raw

// Multiple rows
const users = await db.query<{ firstname: string }>('SELECT firstname FROM users LIMIT 2');

// One row
const [user] = await db.query<User>('SELECT * FROM users LIMIT 1');

// Parameter binding
await db.query<User>('SELECT * FROM users LIMIT ?', [1]);

Transactions

Managed transaction

A managed transaction will automatically commit if the supplied callback doesn't throw/reject any error. If the callback throws/rejects, it will rollback the transaction, and re-throw the error that caused the rollback.

Successful example
await db.transaction(async t => {
    await t.insert<CreateUser>('users', { email: 'bob@test.com' });
    await t.insert<CreateUser>('users', { email: 'alice@test.com' });
});
BEGIN;
INSERT INTO `users` (`email`) VALUES ('bob@test.com');
INSERT INTO `users` (`email`) VALUES ('alice@test.com');
COMMIT;
Rollback example
try {
    await db.transaction(async t => {
        await t.insert<CreateUser>('users', { email: 'bob@test.com' });
        throw new Error('Oops')
        await t.insert<CreateUser>('users', { email: 'alice@test.com' });
    })
} catch (err) {
    // err === Error('Oops')
}
BEGIN;
INSERT INTO `users` (`email`) VALUES ('bob@test.com');
ROLLBACK;
Manual transactions
// Manual transaction (user controls commit/rollback)
const transaction = await db.begin();
await transaction.insert<CreateUser>('users', { email: 'bob@test.com' });
await transaction.insert<CreateUser>('users', { email: 'alice@test.com' });
await transaction.commit(); // Or rollback()
BEGIN;
INSERT INTO `users` (`email`) VALUES ('bob@test.com');
INSERT INTO `users` (`email`) VALUES ('alice@test.com');
COMMIT;

Future/TODO

  • Increase performance by optimizing hot code paths.
  • Return affected row count.
  • Make the library work with more databases.
  • Be able to generate SQL queries in specific formats/flavours.
  • Support for more complex condition objects.
  • Run tests with an actual database connection.

Readme

Keywords

none

Package Sidebar

Install

npm i mysql2-extended

Weekly Downloads

2

Version

1.4.0

License

GPL-3.0

Unpacked Size

49.3 kB

Total Files

12

Last publish

Collaborators

  • zappen999