Шпаргалка по Knex.js

SQL конструктор запросов для Node.js - запросы, схемы, миграции и транзакции.

Установка и настройка

Установите Knex.js и настройте подключение к базе данных

# Установите knex и ваш драйвер базы данных
npm install knex --save

# Драйверы баз данных (выберите один)
npm install pg          # PostgreSQL
npm install mysql2      # MySQL
npm install sqlite3     # SQLite
npm install tedious     # MSSQL
Установка пакетов
const knex = require('knex')({
  client: 'mysql2',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'your_database_user',
    password: 'your_database_password',
    database: 'myapp_test'
  },
  pool: { min: 0, max: 7 }
});
Базовая конфигурация
// Строка подключения
const knex = require('knex')({
  client: 'pg',
  connection: process.env.DATABASE_URL,
  searchPath: ['knex', 'public']
});

// SQLite в памяти
const knex = require('knex')({
  client: 'sqlite3',
  connection: { filename: ':memory:' },
  useNullAsDefault: true
});
Альтернативные методы подключения

Базовые запросы

Операции SELECT, INSERT, UPDATE и DELETE

// Выбрать все
knex('users').select('*')
// SELECT * FROM users

// Выбрать конкретные столбцы
knex('users').select('id', 'name', 'email')
// SELECT id, name, email FROM users

// Выбрать с псевдонимом
knex('users').select('id', 'name as full_name')
// SELECT id, name as full_name FROM users

// Первый результат
knex('users').first('*')
// SELECT * FROM users LIMIT 1
Запросы SELECT
// Вставить одну строку
knex('users').insert({ name: 'John', email: 'john@example.com' })

// Вставить несколько строк
knex('users').insert([
  { name: 'John', email: 'john@example.com' },
  { name: 'Jane', email: 'jane@example.com' }
])

// Вставка с возвращаемым значением (PostgreSQL)
knex('users')
  .insert({ name: 'John', email: 'john@example.com' })
  .returning('id')
Запросы INSERT
// Обновить все строки
knex('users').update({ status: 'active' })

// Обновить конкретные строки
knex('users')
  .where('id', 1)
  .update({ name: 'John Updated' })

// Обновление с возвращаемым значением
knex('users')
  .where('id', 1)
  .update({ name: 'John' })
  .returning('*')
Запросы UPDATE
// Удалить все строки
knex('users').del()

// Удалить конкретные строки
knex('users').where('id', 1).del()

// Удаление с возвращаемым значением
knex('users')
  .where('id', 1)
  .del()
  .returning('*')
Запросы DELETE

Клаузы WHERE

Фильтрация данных с различными условиями

// Простой where
knex('users').where('id', 1)
knex('users').where({ id: 1, status: 'active' })

// where с оператором
knex('users').where('age', '>', 18)
knex('users').where('created_at', '<=', '2024-01-01')

// Несколько условий (AND)
knex('users')
  .where('status', 'active')
  .where('age', '>', 18)

// Условия OR
knex('users')
  .where('status', 'active')
  .orWhere('role', 'admin')
Базовый WHERE
// Где IN
knex('users').whereIn('id', [1, 2, 3])

// Где NOT IN
knex('users').whereNotIn('status', ['banned', 'deleted'])

// Где NULL
knex('users').whereNull('deleted_at')

// Где NOT NULL
knex('users').whereNotNull('email')

// Где BETWEEN
knex('users').whereBetween('age', [18, 65])

// Где NOT BETWEEN
knex('users').whereNotBetween('age', [0, 18])
Продвинутый WHERE
// Сложные вложенные условия
knex('users')
  .where('status', 'active')
  .where(function() {
    this.where('role', 'admin')
      .orWhere('role', 'moderator')
  })
// WHERE status = 'active' AND (role = 'admin' OR role = 'moderator')

// where EXISTS
knex('users')
  .whereExists(function() {
    this.select('*')
      .from('orders')
      .whereRaw('orders.user_id = users.id')
  })

// raw WHERE
knex('users').whereRaw('age > ?', [18])
Сложный WHERE

Объединения

Объединение данных из нескольких таблиц

// INNER JOIN
knex('users')
  .join('orders', 'users.id', '=', 'orders.user_id')
  .select('users.name', 'orders.total')

// LEFT JOIN
knex('users')
  .leftJoin('orders', 'users.id', 'orders.user_id')
  .select('users.name', 'orders.total')

// RIGHT JOIN
knex('users')
  .rightJoin('orders', 'users.id', 'orders.user_id')

// FULL OUTER JOIN
knex('users')
  .fullOuterJoin('orders', 'users.id', 'orders.user_id')
Базовые объединения
// Объединение с несколькими условиями
knex('users')
  .join('orders', function() {
    this.on('users.id', '=', 'orders.user_id')
      .andOn('orders.status', '=', knex.raw('?', ['completed']))
  })

// Объединение с условием OR
knex('users')
  .join('orders', function() {
    this.on('users.id', '=', 'orders.user_id')
      .orOn('users.email', '=', 'orders.email')
  })

// Кросс‑join
knex('users').crossJoin('roles')
Продвинутые объединения

Агрегации и группировка

Агрегатные функции и операции GROUP BY

// Подсчёт
knex('users').count('id as total')

// Подсчёт уникальных
knex('users').countDistinct('email')

// Сумма
knex('orders').sum('total as revenue')

// Среднее
knex('orders').avg('total as average_order')

// Минимум и максимум
knex('orders').min('total as min_order')
knex('orders').max('total as max_order')
Агрегатные функции
// Группировка по
knex('orders')
  .select('user_id')
  .count('id as order_count')
  .groupBy('user_id')

// Группировка по нескольким столбцам
knex('orders')
  .select('user_id', 'status')
  .sum('total as revenue')
  .groupBy('user_id', 'status')

// Условие HAVING
knex('orders')
  .select('user_id')
  .count('id as order_count')
  .groupBy('user_id')
  .having('order_count', '>', 5)

// HAVING с raw
knex('orders')
  .select('user_id')
  .sum('total as revenue')
  .groupBy('user_id')
  .havingRaw('SUM(total) > ?', [1000])
GROUP BY и HAVING

Сортировка и ограничение

Сортировка и пагинация результатов запросов

// Сортировка по возрастанию
knex('users').orderBy('name')
knex('users').orderBy('name', 'asc')

// Сортировка по убыванию
knex('users').orderBy('created_at', 'desc')

// Сортировка по нескольким столбцам
knex('users')
  .orderBy('status', 'asc')
  .orderBy('created_at', 'desc')

// Сортировка массивом
knex('users').orderBy([
  { column: 'status', order: 'asc' },
  { column: 'name', order: 'desc' }
])

// Сортировка raw
knex('users').orderByRaw('age DESC NULLS LAST')
Сортировка
// Ограничение (limit)
knex('users').limit(10)

// Смещение (offset)
knex('users').offset(20)

// Пагинация
knex('users')
  .limit(10)
  .offset(20)

// DISTINCT
knex('users').distinct('email')

// DISTINCT на нескольких столбцах
knex('users').distinct('email', 'status')
Ограничение и пагинация

Схема — создание таблиц

Определение и создание таблиц базы данных

// Создать таблицу
knex.schema.createTable('users', function(table) {
  table.increments('id').primary();
  table.string('name', 100).notNullable();
  table.string('email', 255).unique().notNullable();
  table.integer('age');
  table.boolean('is_active').defaultTo(true);
  table.timestamps(true, true);
});

// Создать таблицу, если её нет
knex.schema.createTableIfNotExists('users', function(table) {
  table.increments('id');
  table.string('email').unique();
});
Базовое создание таблицы
// Common column types
table.increments('id')              // Автоинкрементный ID
table.integer('votes')              // Целое число
table.bigInteger('big_votes')       // Большое целое число
table.text('description')           // Текст
table.string('name', 255)           // Строка с длиной
table.float('amount')               // Число с плавающей точкой
table.decimal('price', 8, 2)        // Десятичное (8,2)
table.boolean('is_admin')           // Булево
table.date('birth_date')            // Дата
table.datetime('created_at')        // Дата и время
table.time('alarm_time')            // Время
table.timestamp('updated_at')       // Метка времени
table.json('metadata')              // JSON
table.jsonb('data')                 // JSONB (PostgreSQL)
table.uuid('id')                    // UUID
table.enum('status', ['active', 'inactive'])  // Перечисление
Типы столбцов
// Модификаторы столбцов
table.string('email')
  .notNullable()           // NOT NULL
  .unique()                // UNIQUE
  .defaultTo('default')    // значение по умолчанию
  .unsigned()              // UNSIGNED (для чисел)
  .index()                 // Добавить индекс
  .comment('Email address') // Добавить комментарий

// Внешние ключи
table.integer('user_id')
  .unsigned()
  .notNullable()
  .references('id')
  .inTable('users')
  .onDelete('CASCADE')
  .onUpdate('CASCADE');

// Вспомогательные функции временных меток
table.timestamps(true, true)  // created_at, updated_at
table.timestamp('created_at').defaultTo(knex.fn.now())
Модификаторы столбцов и ограничения

Схема — изменение таблиц

Изменение существующих структур таблиц

// Добавить столбец
knex.schema.table('users', function(table) {
  table.string('phone', 20);
});

// Добавить несколько столбцов
knex.schema.table('users', function(table) {
  table.string('phone', 20);
  table.string('address', 255);
  table.integer('zip_code');
});

// Изменить столбец
knex.schema.alterTable('users', function(table) {
  table.string('email', 500).alter();
});

// Переименовать столбец
knex.schema.table('users', function(table) {
  table.renameColumn('name', 'full_name');
});
Модификация таблиц
// Удалить столбец
knex.schema.table('users', function(table) {
  table.dropColumn('phone');
});

// Удалить несколько столбцов
knex.schema.table('users', function(table) {
  table.dropColumns('phone', 'address');
});

// Удалить таблицу
knex.schema.dropTable('users');

// Удалить таблицу, если существует
knex.schema.dropTableIfExists('users');

// Переименовать таблицу
knex.schema.renameTable('users', 'customers');
Удаление и переименование
// Проверить, существует ли таблица
knex.schema.hasTable('users')
  .then(exists => {
    if (!exists) {
      return knex.schema.createTable('users', table => {
        table.increments('id');
      });
    }
  });

// Проверить, существует ли столбец
knex.schema.hasColumn('users', 'email')
  .then(exists => {
    if (!exists) {
      return knex.schema.table('users', table => {
        table.string('email');
      });
    }
  });
Проверки схемы

Схема — индексы и внешние ключи

Создание и управление индексами базы данных и связями

// Создать индекс
knex.schema.table('users', function(table) {
  table.index('email');
});

// Создать именованный индекс
knex.schema.table('users', function(table) {
  table.index('email', 'idx_users_email');
});

// Составной индекс
knex.schema.table('users', function(table) {
  table.index(['status', 'created_at'], 'idx_status_created');
});

// Уникальный индекс
knex.schema.table('users', function(table) {
  table.unique('email');
});

// Удалить индекс
knex.schema.table('users', function(table) {
  table.dropIndex('email');
  table.dropIndex([], 'idx_users_email'); // по имени
});
Индексы
// Добавить внешний ключ
knex.schema.table('orders', function(table) {
  table.foreign('user_id')
    .references('id')
    .inTable('users');
});

// Внешний ключ с действиями
knex.schema.table('orders', function(table) {
  table.foreign('user_id')
    .references('id')
    .inTable('users')
    .onDelete('CASCADE')
    .onUpdate('RESTRICT');
});

// Именованный внешний ключ
knex.schema.table('orders', function(table) {
  table.foreign('user_id', 'fk_orders_user')
    .references('users.id');
});

// Удалить внешний ключ
knex.schema.table('orders', function(table) {
  table.dropForeign('user_id');
  table.dropForeign([], 'fk_orders_user'); // по имени
});
Внешние ключи

Транзакции

Выполнение нескольких запросов атомарно

// Базовая транзакция
knex.transaction(async (trx) => {
  await trx('users').insert({ name: 'John' });
  await trx('orders').insert({ user_id: 1, total: 100 });
})
.then(() => console.log('Transaction committed'))
.catch((err) => console.error('Transaction failed:', err));

// Транзакция с возвращаемым значением
const userId = await knex.transaction(async (trx) => {
  const [id] = await trx('users')
    .insert({ name: 'John' })
    .returning('id');
  
  await trx('profiles')
    .insert({ user_id: id, bio: 'Hello' });
  
  return id;
});
Базовые транзакции
// Ручное управление транзакцией
const trx = await knex.transaction();

try {
  await trx('users').insert({ name: 'John' });
  await trx('orders').insert({ user_id: 1, total: 100 });
  await trx.commit();
} catch (err) {
  await trx.rollback();
  throw err;
}

// Транзакция с точками сохранения
knex.transaction(async (trx) => {
  await trx('users').insert({ name: 'John' });
  
  const savepoint = await trx.savepoint();
  try {
    await trx('orders').insert({ user_id: 1, total: 100 });
    await savepoint.release();
  } catch (err) {
    await savepoint.rollback();
  }
});
Продвинутое управление транзакциями

Необработанные запросы

Выполнение необработанного SQL при необходимости

// Простой необработанный запрос
knex.raw('SELECT * FROM users WHERE id = ?', [1])

// Необработанный запрос с именованными привязками
knex.raw('SELECT * FROM users WHERE id = :userId', {
  userId: 1
})

// Необработанный запрос в select
knex('users')
  .select(knex.raw('COUNT(*) as total'))
  .where('status', 'active')

// Необработанный запрос в where
knex('users')
  .whereRaw('age > ?', [18])
  .orWhereRaw('status = ?', ['admin'])
Базовые необработанные запросы
// Необработанное обновление
knex('users')
  .update({
    updated_at: knex.raw('NOW()'),
    views: knex.raw('views + 1')
  })
  .where('id', 1)

// Необработанная вставка
knex('users').insert({
  name: 'John',
  created_at: knex.raw('CURRENT_TIMESTAMP')
})

// Использование knex.ref для ссылок на столбцы
knex('users')
  .where('created_at', '>', knex.ref('updated_at'))
Необработанные запросы в запросах

Миграции

Контроль версий схемы базы данных

# Создать миграцию
npx knex migrate:make create_users_table

# Запустить миграции
npx knex migrate:latest

# Откатить последнюю партию
npx knex migrate:rollback

# Откатить все миграции
npx knex migrate:rollback --all

# Проверить статус миграций
npx knex migrate:status

# Запустить конкретную миграцию
npx knex migrate:up 001_create_users.js
Команды миграций
// Структура файла миграции
exports.up = function(knex) {
  return knex.schema.createTable('users', function(table) {
    table.increments('id').primary();
    table.string('name', 100).notNullable();
    table.string('email', 255).unique().notNullable();
    table.timestamp('created_at').defaultTo(knex.fn.now());
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable('users');
};
Пример файла миграции
// Миграция с несколькими операциями
exports.up = async function(knex) {
  // Создать таблицу users
  await knex.schema.createTable('users', table => {
    table.increments('id');
    table.string('email').unique();
  });
  
  // Создать таблицу orders
  await knex.schema.createTable('orders', table => {
    table.increments('id');
    table.integer('user_id').unsigned();
    table.foreign('user_id').references('users.id');
  });
  
  // Заполнить начальными данными
  await knex('users').insert([
    { email: 'admin@example.com' }
  ]);
};

exports.down = async function(knex) {
  await knex.schema.dropTable('orders');
  await knex.schema.dropTable('users');
};
Сложная миграция

Сиды

Заполнение базы данных тестовыми данными

# Создать файл сида
npx knex seed:make 01_users

# Запустить все сиды
npx knex seed:run

# Запустить конкретный сид
npx knex seed:run --specific=01_users.js
Команды сидов
// Базовый файл сида
exports.seed = async function(knex) {
  // Удалить существующие записи
  await knex('users').del();
  
  // Вставить данные сида
  await knex('users').insert([
    { name: 'John Doe', email: 'john@example.com' },
    { name: 'Jane Smith', email: 'jane@example.com' },
    { name: 'Bob Johnson', email: 'bob@example.com' }
  ]);
};
Базовый сид
// Сид с отношениями
exports.seed = async function(knex) {
  // Очистить таблицы в правильном порядке
  await knex('orders').del();
  await knex('users').del();
  
  // Вставить пользователей и получить их ID
  const [userId1] = await knex('users')
    .insert({ name: 'John', email: 'john@example.com' })
    .returning('id');
  
  const [userId2] = await knex('users')
    .insert({ name: 'Jane', email: 'jane@example.com' })
    .returning('id');
  
  // Вставить связанные заказы
  await knex('orders').insert([
    { user_id: userId1, total: 100.50 },
    { user_id: userId1, total: 75.25 },
    { user_id: userId2, total: 200.00 }
  ]);
};
Сид с отношениями

Поддержка TypeScript

Типобезопасные запросы с TypeScript

// Определить типы таблиц
interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
  updated_at: string;
}

interface UserInsert {
  name: string;
  email: string;
}

interface UserUpdate {
  name?: string;
  email?: string;
  updated_at?: string;
}
Определения типов
// Расширить типы Knex
import { Knex } from 'knex';

declare module 'knex/types/tables' {
  interface Tables {
    users: User;
    users_composite: Knex.CompositeTableType<
      User,
      UserInsert,
      UserUpdate
    >;
  }
}

// Типобезопасные запросы
const users = await knex('users').select('*');
// users is typed as User[]

const newUser = await knex('users_composite')
  .insert({ name: 'John', email: 'john@example.com' })
  .returning('*');
Расширение типов
// Универсальные типы запросов
const getUserById = async (id: number): Promise<User | undefined> => {
  return knex<User>('users')
    .where({ id })
    .first();
};

const createUser = async (data: UserInsert): Promise<User> => {
  const [user] = await knex<User>('users')
    .insert(data)
    .returning('*');
  return user;
};

const updateUser = async (
  id: number,
  data: UserUpdate
): Promise<User | undefined> => {
  const [user] = await knex<User>('users')
    .where({ id })
    .update(data)
    .returning('*');
  return user;
};
Типизированные функции запросов

Продвинутые возможности

Продвинутые возможности Knex.js

// Upsert (вставка или обновление)
knex('users')
  .insert({ id: 1, name: 'John', email: 'john@example.com' })
  .onConflict('id')
  .merge()

// Upsert с указанием конкретных столбцов
knex('users')
  .insert({ id: 1, name: 'John', email: 'john@example.com' })
  .onConflict('email')
  .merge(['name'])

// Insert ignore (MySQL)
knex('users')
  .insert({ email: 'john@example.com' })
  .onConflict('email')
  .ignore()
Операции Upsert
// WITH (Общие табличные выражения)
knex.with('active_users', (qb) => {
  qb.select('*').from('users').where('status', 'active');
})
.select('*')
.from('active_users')
.where('age', '>', 18);

// Рекурсивный CTE
knex.withRecursive('tree', (qb) => {
  qb.select('*').from('categories').where('parent_id', null)
    .union((qb2) => {
      qb2.select('c.*')
        .from('categories as c')
        .join('tree as t', 'c.parent_id', 't.id');
    });
})
.select('*').from('tree');
Общие табличные выражения
// Подзапросы
knex('users')
  .whereIn('id', function() {
    this.select('user_id').from('orders').where('total', '>', 100);
  })

// Подзапрос в select
knex('users')
  .select('name')
  .select(
    knex('orders')
      .count('*')
      .where('orders.user_id', knex.raw('??', ['users.id']))
      .as('order_count')
  )

// Подзапрос в from
knex
  .select('*')
  .from(
    knex('users')
      .where('status', 'active')
      .as('active_users')
  )
Подзапросы
// Оконные функции
knex('orders')
  .select('*')
  .select(
    knex.raw('ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) as row_num')
  )

// Операции с JSON (PostgreSQL)
knex('users')
  .select('name')
  .select(knex.raw("metadata->>'age' as age"))
  .where(knex.raw("metadata->>'city'"), 'New York')

// Операции с массивами (PostgreSQL)
knex('users')
  .whereRaw('? = ANY(tags)', ['javascript'])
Продвинутые возможности SQL

Управление соединениями и пулом

Управление соединениями с базой данных и пулом

// Настройка пула соединений
const knex = require('knex')({
  client: 'pg',
  connection: {
    host: '127.0.0.1',
    user: 'user',
    password: 'password',
    database: 'myapp'
  },
  pool: {
    min: 2,
    max: 10,
    acquireTimeoutMillis: 30000,
    idleTimeoutMillis: 30000
  }
});

// Уничтожить пул соединений
await knex.destroy();

// Переинициализировать
knex.initialize();
Конфигурация пула
// Хук afterCreate соединения
const knex = require('knex')({
  client: 'pg',
  connection: { /* ... */ },
  pool: {
    afterCreate: function(conn, done) {
      conn.query('SET timezone="UTC";', function(err) {
        done(err, conn);
      });
    }
  }
});

// Динамическое соединение
const knex = require('knex')({
  client: 'mysql',
  connection: async () => {
    const token = await getAuthToken();
    return {
      host: 'localhost',
      user: 'user',
      password: token,
      database: 'myapp'
    };
  }
});
Хуки соединения
// Отладка запросов
const knex = require('knex')({
  client: 'pg',
  connection: { /* ... */ },
  debug: true  // Записывать все запросы
});

// Отладка конкретного запроса
knex('users')
  .select('*')
  .debug()
  .then(rows => console.log(rows));

// Получить SQL без выполнения
const sql = knex('users')
  .where('id', 1)
  .toSQL();
console.log(sql.sql);      // SQL string
console.log(sql.bindings); // Parameters
Отладка
Шпаргалка по Knex.js