TypeORM для JavaScript (Node.js): полное руководство с примерами

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

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

Начать курс

TypeORM для JavaScript (Node.js): полное руководство

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

TypeORM — это мощная Object-Relational Mapping (ORM) библиотека, предназначенная для работы с базами данных в среде Node.js. Она поддерживает как TypeScript, так и JavaScript, предоставляя разработчикам удобный способ взаимодействия с реляционными базами данных (PostgreSQL, MySQL, MariaDB, SQLite, Microsoft SQL Server, Oracle и CockroachDB) через объектно-ориентированный подход. Вместо написания сырых SQL-запросов, вы работаете с сущностями (Entity) — обычными классами JavaScript, которые автоматически маппятся на таблицы базы данных.

Зачем нужна TypeORM? Во-первых, она значительно ускоряет разработку: вам не нужно вручную писать SQL-запросы для CRUD-операций (Create, Read, Update, Delete). Во-вторых, она обеспечивает безопасность типов (если вы используете TypeScript) и защиту от SQL-инъекций благодаря использованию параметризованных запросов. В-третьих, TypeORM предоставляет мощный Query Builder для сложных запросов, миграции для управления схемой базы данных, а также поддержку отношений между таблицами (один-к-одному, один-ко-многим, многие-ко-многим). Это делает её идеальным выбором для построения масштабируемых и поддерживаемых приложений.

В отличие от некоторых других ORM, TypeORM активно использует декораторы (в TypeScript) или аннотации (в JavaScript через экспериментальные возможности), что делает код декларативным и интуитивно понятным. Библиотека также поддерживает Active Record и Data Mapper паттерны, позволяя разработчику выбрать наиболее подходящий стиль работы. Благодаря своей гибкости и богатому функционалу, TypeORM является одной из самых популярных ORM в экосистеме Node.js.

2. Установка

Для установки TypeORM в ваш проект Node.js используйте менеджер пакетов npm. Вам также потребуется установить драйвер для вашей базы данных. Ниже приведены команды для наиболее популярных СУБД.

# Установка TypeORM
npm install typeorm --save

# Установка драйвера для PostgreSQL
npm install pg --save

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

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

# Установка драйвера для Microsoft SQL Server
npm install mssql --save

# Установка драйвера для CockroachDB
npm install pg --save

# Для TypeScript также установите reflect-metadata и типы
npm install reflect-metadata --save
npm install @types/node --save-dev

После установки, убедитесь, что в вашем tsconfig.json (если используете TypeScript) включены следующие настройки:

{
  "emitDecoratorMetadata": true,
  "experimentalDecorators": true
}

Для JavaScript (Node.js) эти настройки не требуются, но вы можете использовать экспериментальные декораторы, если хотите.

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

Давайте создадим простое приложение, которое подключается к базе данных SQLite (файловая БД, не требует установки сервера) и выполняет базовые операции.

Создайте файл index.js и добавьте следующий код:

const { createConnection, EntitySchema } = require("typeorm");

// Определяем сущность (Entity) через EntitySchema
const UserSchema = new EntitySchema({
    name: "User",
    tableName: "users",
    columns: {
        id: {
            primary: true,
            type: "int",
            generated: true
        },
        firstName: {
            type: "varchar"
        },
        lastName: {
            type: "varchar"
        },
        age: {
            type: "int"
        }
    }
});

async function main() {
    // Создаем подключение к базе данных
    const connection = await createConnection({
        type: "sqlite",
        database: "test.db",
        synchronize: true, // Автоматически создает таблицы (только для разработки!)
        logging: false,
        entities: [UserSchema]
    });

    console.log("Подключение к базе данных установлено.");

    // Получаем репозиторий для работы с сущностью User
    const userRepository = connection.getRepository(UserSchema);

    // Создаем нового пользователя
    const newUser = userRepository.create({
        firstName: "Иван",
        lastName: "Петров",
        age: 30
    });

    // Сохраняем пользователя в базу данных
    await userRepository.save(newUser);
    console.log("Пользователь сохранен:", newUser);

    // Находим всех пользователей
    const allUsers = await userRepository.find();
    console.log("Все пользователи:", allUsers);

    // Закрываем подключение
    await connection.close();
    console.log("Подключение закрыто.");
}

main().catch(error => console.error("Ошибка:", error));

Запустите скрипт командой node index.js. В текущей директории будет создан файл test.db (база данных SQLite) и таблица users с одной записью.

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

Ниже приведены 8 ключевых методов и классов TypeORM, которые используются в повседневной разработке. Для каждого метода указана сигнатура, описание и пример кода на JavaScript.

4.1. createConnection()

Сигнатура: createConnection(options?: ConnectionOptions | string): Promise<Connection>

Что делает: Создает и возвращает подключение к базе данных. Принимает объект конфигурации или имя заранее определенного подключения.

const connection = await createConnection({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "password",
    database: "test",
    entities: [__dirname + "/entities/*.js"],
    synchronize: true
});
console.log("Подключено к MySQL");

4.2. getRepository()

Сигнатура: connection.getRepository(target: EntityTarget<Entity>): Repository<Entity>

Что делает: Возвращает репозиторий для работы с определенной сущностью. Репозиторий предоставляет методы для CRUD-операций.

const userRepository = connection.getRepository(User);

4.3. repository.find()

Сигнатура: find(options?: FindManyOptions<Entity>): Promise<Entity[]>

Что делает: Находит все записи, соответствующие переданным условиям. Если условия не указаны, возвращает все записи из таблицы.

// Найти всех пользователей старше 25 лет
const users = await userRepository.find({
    where: { age: MoreThan(25) }
});
console.log(users);

4.4. repository.findOne()

Сигнатура: findOne(id: string | number | Date | ObjectID, options?: FindOneOptions<Entity>): Promise<Entity | undefined>

Что делает: Находит одну запись по первичному ключу или по переданным условиям. Возвращает undefined, если запись не найдена.

// Найти пользователя с id = 1
const user = await userRepository.findOne(1);
console.log(user);

// Найти пользователя по имени
const userByName = await userRepository.findOne({
    where: { firstName: "Иван" }
});
console.log(userByName);

4.5. repository.save()

Сигнатура: save(entity: Entity | Entity[], options?: SaveOptions): Promise<Entity | Entity[]>

Что делает: Сохраняет одну или несколько сущностей в базу данных. Если сущность новая (не имеет id), выполняется INSERT. Если сущность уже существует (имеет id), выполняется UPDATE.

// Создание нового пользователя
const newUser = new User();
newUser.firstName = "Петр";
newUser.lastName = "Сидоров";
newUser.age = 28;
await userRepository.save(newUser);

// Обновление существующего пользователя
const existingUser = await userRepository.findOne(1);
if (existingUser) {
    existingUser.age = 31;
    await userRepository.save(existingUser);
}

4.6. repository.remove()

Сигнатура: remove(entity: Entity | Entity[], options?: RemoveOptions): Promise<Entity | Entity[]>

Что делает: Удаляет одну или несколько сущностей из базы данных. Сущности должны быть предварительно загружены (иметь id).

// Удалить пользователя с id = 1
const userToRemove = await userRepository.findOne(1);
if (userToRemove) {
    await userRepository.remove(userToRemove);
    console.log("Пользователь удален");
}

4.7. createQueryBuilder()

Сигнатура: repository.createQueryBuilder(alias?: string): SelectQueryBuilder<Entity>

Что делает: Создает Query Builder для построения сложных SQL-запросов с использованием цепочки методов. Позволяет выполнять JOIN, подзапросы, агрегации и другие операции.

// Найти всех пользователей с возрастом > 18, отсортированных по фамилии
const users = await userRepository.createQueryBuilder("user")
    .where("user.age > :age", { age: 18 })
    .orderBy("user.lastName", "ASC")
    .getMany();
console.log(users);

4.8. getManager()

Сигнатура: connection.getManager(): EntityManager

Что делает: Возвращает EntityManager — центральный объект для управления всеми сущностями. Он предоставляет методы, аналогичные репозиторию, но работает на уровне менеджера, а не конкретной сущности.

const entityManager = connection.getManager();

// Использование EntityManager для поиска
const users = await entityManager.find(User, {
    where: { age: LessThan(30) }
});
console.log(users);

// Использование EntityManager для транзакции
await entityManager.transaction(async transactionalEntityManager => {
    const user1 = await transactionalEntityManager.findOne(User, 1);
    const user2 = await transactionalEntityManager.findOne(User, 2);
    // Выполнение операций в рамках одной транзакции
});

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

Рассмотрим реальный пример: создание простого REST API для управления списком задач (Todo List) с использованием Express.js и TypeORM. Мы будем использовать SQLite для простоты.

Сначала установите дополнительные зависимости:

npm install express body-parser --save

Создайте файл app.js со следующим содержимым:

const express = require('express');
const bodyParser = require('body-parser');
const { createConnection, EntitySchema } = require('typeorm');

// Определяем сущность Task
const TaskSchema = new EntitySchema({
    name: "Task",
    tableName: "tasks",
    columns: {
        id: {
            primary: true,
            type: "int",
            generated: true
        },
        title: {
            type: "varchar"
        },
        description: {
            type: "text",
            nullable: true
        },
        isCompleted: {
            type: "boolean",
            default: false
        },
        createdAt: {
            type: "datetime",
            createDate: true
        }
    }
});

const app = express();
app.use(bodyParser.json());

let taskRepository;

// Инициализация подключения и запуск сервера
async function start() {
    const connection = await createConnection({
        type: "sqlite",
        database: "todo.db",
        synchronize: true,
        logging: false,
        entities: [TaskSchema]
    });

    taskRepository = connection.getRepository(TaskSchema);
    console.log("База данных подключена");

    // Маршруты API

    // GET /tasks - получить все задачи
    app.get('/tasks', async (req, res) => {
        try {
            const tasks = await taskRepository.find();
            res.json(tasks);
        } catch (error) {
            res.status(500).json({ error: error.message });
        }
    });

    // GET /tasks/:id - получить задачу по ID
    app.get('/tasks/:id', async (req, res) => {
        try {
            const task = await taskRepository.findOne(parseInt(req.params.id));
            if (!task) {
                return res.status(404).json({ error: "Задача не найдена" });
            }
            res.json(task);
        } catch (error) {
            res.status(500).json({ error: error.message });
        }
    });

    // POST /tasks - создать новую задачу
    app.post('/tasks', async (req, res) => {
        try {
            const { title, description } = req.body;
            if (!title) {
                return res.status(400).json({ error: "Поле title обязательно" });
            }

            const newTask = taskRepository.create({
                title,
                description: description || "",
                isCompleted: false
            });

            const savedTask = await taskRepository.save(newTask);
            res.status(201).json(savedTask);
        } catch (error) {
            res.status(500).json({ error: error.message });
        }
    });

    // PUT /tasks/:id - обновить задачу
    app.put('/tasks/:id', async (req, res) => {
        try {
            const taskId = parseInt(req.params.id);
            const task = await taskRepository.findOne(taskId);
            if (!task) {
                return res.status(404).json({ error: "Задача не найдена" });
            }

            // Обновляем только переданные поля
            if (req.body.title !== undefined) task.title =                    

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