ts-sql-query для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

ts-sql-query для TypeScript: полное руководство с примерами

1. Что это и зачем нужна

ts-sql-query — это мощная библиотека для TypeScript, которая позволяет писать SQL-запросы с полной поддержкой типов (type-safe). Она предоставляет удобный DSL (Domain-Specific Language) для построения SQL-запросов, при этом каждый запрос проверяется компилятором TypeScript на соответствие схеме базы данных. Это означает, что ошибки в названиях таблиц, полей или типах данных будут обнаружены на этапе компиляции, а не в рантайме.

Зачем это нужно? В традиционных подходах (сырые строки SQL или ORM) разработчики часто сталкиваются с проблемами: опечатки в именах колонок, несоответствие типов, сложность рефакторинга. ts-sql-query решает эти проблемы, предоставляя строгую типизацию для всех операций: SELECT, INSERT, UPDATE, DELETE, JOIN, подзапросы, агрегатные функции и многое другое. Библиотека поддерживает множество СУБД (PostgreSQL, MySQL, SQLite, MariaDB, MSSQL) и может работать как с драйверами напрямую, так и через пулы соединений.

В отличие от ORM (например, TypeORM или Prisma), ts-sql-query не скрывает SQL за абстракциями, а делает его частью TypeScript-кода. Вы по-прежнему пишете SQL, но с автодополнением, проверкой типов и без риска SQL-инъекций. Это идеальный выбор для проектов, где важна производительность и полный контроль над запросами, но при этом хочется использовать преимущества статической типизации.

2. Установка

Установка производится через npm. Вам потребуется установить саму библиотеку и драйвер для вашей базы данных.

# Установка основной библиотеки
npm install ts-sql-query

# Установка драйвера для PostgreSQL (пример)
npm install pg

# Установка драйвера для MySQL
npm install mysql2

# Установка драйвера для SQLite
npm install better-sqlite3

# Установка драйвера для SQLite (асинхронный)
npm install sql.js

# Установка типов для Node.js (если используете)
npm install --save-dev @types/node

После установки драйвера, вам нужно создать соединение с базой данных. ts-sql-query предоставляет адаптеры для каждого драйвера.

3. Быстрый старт — минимальный рабочий пример

Рассмотрим пример для PostgreSQL. Создадим простую таблицу users и выполним SELECT-запрос.

import { createPool } from 'pg';
import { PostgreSqlConnection } from 'ts-sql-query/connections/PostgreSqlConnection';

// 1. Создаем пул соединений
const pool = createPool({
    host: 'localhost',
    port: 5432,
    database: 'testdb',
    user: 'user',
    password: 'password',
});

// 2. Создаем класс соединения
class DBConnection extends PostgreSqlConnection {
    // Здесь можно определить методы для работы с таблицами
}

// 3. Создаем экземпляр соединения
const connection = new DBConnection(pool);

// 4. Определяем тип для таблицы users
interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

// 5. Создаем объект для работы с таблицей
const users = connection.table('users').columns({
    id: 'id',
    name: 'name',
    email: 'email',
    age: 'age',
});

// 6. Выполняем запрос
async function getUsers() {
    const query = connection
        .selectFrom(users)
        .select(users.id, users.name, users.email)
        .where(users.age.greaterThan(18))
        .orderBy(users.name, 'asc');

    const result = await query.executeSelectMany();
    return result;
}

getUsers().then(users => {
    console.log('Users:', users);
}).catch(err => {
    console.error('Error:', err);
});

В этом примере мы создали соединение, определили таблицу и выполнили SELECT-запрос. Все типы проверяются компилятором.

4. Основные методы/функции/классы

Рассмотрим ключевые методы библиотеки. Каждый метод имеет строгую типизацию и возвращает объект, который можно использовать для дальнейшего построения запроса.

4.1. table()

Сигнатура: connection.table(tableName: string, alias?: string): Table

Что делает: Создает объект для работы с таблицей. Позволяет указать имя таблицы и опциональный псевдоним.

const users = connection.table('users');
const orders = connection.table('orders', 'o'); // с псевдонимом

4.2. columns()

Сигнатура: table.columns(columns: Record<string, string>): TableWithColumns

Что делает: Определяет колонки таблицы. Принимает объект, где ключи — это имена полей в TypeScript, а значения — имена колонок в БД.

const users = connection.table('users').columns({
    id: 'id',
    name: 'name',
    email: 'email',
    age: 'age',
    createdAt: 'created_at',
});

4.3. selectFrom()

Сигнатура: connection.selectFrom(table: TableWithColumns): SelectQuery

Что делает: Начинает построение SELECT-запроса. Принимает таблицу, из которой нужно выбрать данные.

const query = connection.selectFrom(users);

4.4. select()

Сигнатура: selectQuery.select(...columns: Column[]): SelectQuery

Что делает: Указывает, какие колонки включить в результат. Можно передавать несколько колонок.

const query = connection
    .selectFrom(users)
    .select(users.id, users.name, users.email);

4.5. where()

Сигнатура: selectQuery.where(condition: Condition): SelectQuery

Что делает: Добавляет условие WHERE. Принимает объект условия, который можно строить с помощью методов колонок (например, equals(), greaterThan(), like()).

const query = connection
    .selectFrom(users)
    .select(users.id, users.name)
    .where(users.age.greaterThan(18))
    .where(users.name.like('%John%')); // можно комбинировать

4.6. orderBy()

Сигнатура: selectQuery.orderBy(column: Column, direction?: 'asc' | 'desc'): SelectQuery

Что делает: Добавляет сортировку. Можно указать направление (по умолчанию 'asc').

const query = connection
    .selectFrom(users)
    .select(users.id, users.name)
    .orderBy(users.name, 'asc')
    .orderBy(users.id, 'desc'); // множественная сортировка

4.7. limit() и offset()

Сигнатура: selectQuery.limit(limit: number): SelectQuery и selectQuery.offset(offset: number): SelectQuery

Что делает: Добавляет LIMIT и OFFSET для пагинации.

const query = connection
    .selectFrom(users)
    .select(users.id, users.name)
    .limit(10)
    .offset(20);

4.8. innerJoin(), leftJoin(), rightJoin()

Сигнатура: selectQuery.innerJoin(table: TableWithColumns, condition: Condition): SelectQuery

Что делает: Добавляет JOIN. Поддерживаются INNER, LEFT, RIGHT JOIN.

const orders = connection.table('orders').columns({
    id: 'id',
    userId: 'user_id',
    total: 'total',
});

const query = connection
    .selectFrom(users)
    .innerJoin(orders, orders.userId.equals(users.id))
    .select(users.name, orders.total);

4.9. insertInto()

Сигнатура: connection.insertInto(table: TableWithColumns): InsertQuery

Что делает: Начинает построение INSERT-запроса.

const query = connection
    .insertInto(users)
    .values({
        name: 'John Doe',
        email: 'john@example.com',
        age: 30,
    });

await query.executeInsert();

4.10. update()

Сигнатура: connection.update(table: TableWithColumns): UpdateQuery

Что делает: Начинает построение UPDATE-запроса.

const query = connection
    .update(users)
    .set({
        name: 'Jane Doe',
        age: 25,
    })
    .where(users.id.equals(1));

await query.executeUpdate();

4.11. deleteFrom()

Сигнатура: connection.deleteFrom(table: TableWithColumns): DeleteQuery

Что делает: Начинает построение DELETE-запроса.

const query = connection
    .deleteFrom(users)
    .where(users.id.equals(1));

await query.executeDelete();

4.12. executeSelectMany(), executeSelectOne(), executeSelectNone()

Сигнатура: query.executeSelectMany(): Promise<T[]>, executeSelectOne(): Promise<T | null>, executeSelectNone(): Promise<void>

Что делает: Выполняют SELECT-запрос и возвращают результат в разных форматах: массив, один объект или ничего.

const allUsers = await query.executeSelectMany();
const firstUser = await query.executeSelectOne();
await query.executeSelectNone(); // если не нужен результат

5. Реальный практический пример (100-200 строк кода)

Рассмотрим полноценный пример работы с интернет-магазином: таблицы users, products, orders, order_items. Реализуем CRUD-операции и сложный запрос с JOIN.

import { createPool } from 'pg';
import { PostgreSqlConnection } from 'ts-sql-query/connections/PostgreSqlConnection';

// Настройка подключения
const pool = createPool({
    host: 'localhost',
    port: 5432,
    database: 'shop',
    user: 'admin',
    password: 'secret',
});

class DBConnection extends PostgreSqlConnection {}

const connection = new DBConnection(pool);

// Определение таблиц
const users = connection.table('users').columns({
    id: 'id',
    name: 'name',
    email: 'email',
    createdAt: 'created_at',
});

const products = connection.table('products').columns({
    id: 'id',
    name: 'name',
    price: 'price',
    stock: 'stock',
});

const orders = connection.table('orders').columns({
    id: 'id',
    userId: 'user_id',
    total: 'total',
    status: 'status',
    createdAt: 'created_at',
});

const orderItems = connection.table('order_items').columns({
    id: 'id',
    orderId: 'order_id',
    productId: 'product_id',
    quantity: 'quantity',
    price: 'price',
});

// Функция для создания пользователя
async function createUser(name: string, email: string) {
    const query = connection
        .insertInto(users)
        .values({ name, email });
    return await query.executeInsert();
}

// Функция для получения всех заказов пользователя с товарами
async function getUserOrdersWithItems(userId: number) {
    const query = connection
        .selectFrom(orders)
        .innerJoin(orderItems, orderItems.orderId.equals(orders.id))
        .innerJoin(products, products.id.equals(orderItems.productId))
        .select(
            orders.id,
            orders.total,
            orders.status,
            orderItems.quantity,
            products.name,
            products.price
        )
        .where(orders.userId.equals(userId))
        .orderBy(orders.createdAt, 'desc');

    return await query.executeSelectMany();
}

// Функция для обновления статуса заказа
async function updateOrderStatus(orderId: number, status: string) {
    const query = connection
        .update(orders)
        .set({ status })
        .where(orders.id.equals(orderId));
    return await query.executeUpdate();
}

// Функция для удаления товара (если нет в заказах)
async function deleteProductIfNotOrdered(productId: number) {
    // Проверяем, есть ли товар в заказах
    const checkQuery = connection
        .selectFrom(orderItems)
        .select(orderItems.id)
        .where(orderItems.productId.equals(productId))
        .limit(1);
    const existing = await checkQuery.executeSelectOne();
    
    if (existing) {
        throw new Error('Cannot delete product: it is in orders');
    }
    
    const deleteQuery = connection
        .deleteFrom(products)
        .where(products.id.equals(productId));
    return await deleteQuery.executeDelete();
}

//                    

Рекомендаций