Введение: Почему TypeScript и базы данных — идеальная пара
Современная веб-разработка немыслима без работы с базами данных (БД). TypeScript, как строго типизированная надстройка над JavaScript, предлагает разработчикам мощный инструмент для создания надежных и масштабируемых приложений. В этой статье мы на практике разберем, как подключать и использовать две самые популярные базы данных — реляционную PostgreSQL и документо-ориентированную MongoDB — в экосистеме TypeScript.
Вы узнаете, как настроить ORM/ODM, написать типизированные модели и выполнить базовые CRUD-операции. Материал рассчитан на разработчиков, которые уже знакомы с основами TypeScript и Node.js.
1. Настройка проекта и установка зависимостей
Прежде чем писать код, создадим новый проект и установим необходимые пакеты. Для работы с PostgreSQL мы будем использовать TypeORM — один из самых популярных ORM для TypeScript. Для MongoDB — Mongoose.
Инициализация проекта
mkdir typescript-db-practice
cd typescript-db-practice
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init
Установка зависимостей для PostgreSQL
npm install typeorm reflect-metadata pg
npm install @types/node --save-dev
Установка зависимостей для MongoDB
npm install mongoose
npm install @types/mongoose --save-dev
После установки в файле tsconfig.json обязательно включите опции "experimentalDecorators": true и "emitDecoratorMetadata": true — они необходимы для работы TypeORM.
2. Работа с PostgreSQL через TypeORM
TypeORM поддерживает как Active Record, так и Data Mapper паттерны. Мы рассмотрим Data Mapper, так как он дает больше гибкости и лучше сочетается с чистой архитектурой.
Создание сущности (Entity)
Создадим модель пользователя. Каждая сущность — это класс, декорированный специальными аннотациями.
// src/entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Column({ unique: true })
email: string;
@Column({ select: false })
password: string;
@CreateDateColumn()
createdAt: Date;
}
Настройка подключения и репозиторий
Создадим файл для инициализации базы данных и репозиторий для работы с пользователями.
// src/data-source.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { User } from "./entities/User";
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "password",
database: "test_db",
synchronize: true, // Авто-создание таблиц (только для разработки!)
logging: false,
entities: [User],
migrations: [],
subscribers: [],
});
// src/repositories/userRepository.ts
import { AppDataSource } from "../data-source";
import { User } from "../entities/User";
export const userRepository = AppDataSource.getRepository(User);
CRUD операции на практике
// src/index.ts
import { AppDataSource } from "./data-source";
import { userRepository } from "./repositories/userRepository";
async function main() {
await AppDataSource.initialize();
console.log("PostgreSQL connected!");
// CREATE
const newUser = userRepository.create({
name: "Иван Петров",
email: "ivan@example.com",
password: "hashed_password"
});
await userRepository.save(newUser);
console.log("User created:", newUser);
// READ
const users = await userRepository.find();
console.log("All users:", users);
// UPDATE
await userRepository.update(1, { name: "Иван Сидоров" });
// DELETE
await userRepository.delete(1);
}
main().catch(console.error);
Важно: В production используйте миграции вместо synchronize: true.
3. Работа с MongoDB через Mongoose
Mongoose — это ODM (Object Document Mapper) для MongoDB. Он предоставляет удобную схему для валидации данных и типизации.
Определение схемы и модели
// src/models/Product.ts
import mongoose, { Schema, Document } from 'mongoose';
export interface IProduct extends Document {
name: string;
price: number;
inStock: boolean;
tags: string[];
createdAt: Date;
}
const ProductSchema: Schema = new Schema({
name: { type: String, required: true, maxlength: 200 },
price: { type: Number, required: true, min: 0 },
inStock: { type: Boolean, default: true },
tags: { type: [String], default: [] },
createdAt: { type: Date, default: Date.now }
});
export const Product = mongoose.model<IProduct>('Product', ProductSchema);
Подключение к базе и CRUD
// src/mongo-index.ts
import mongoose from 'mongoose';
import { Product } from './models/Product';
async function runMongo() {
await mongoose.connect('mongodb://localhost:27017/shop');
console.log("MongoDB connected!");
// CREATE
const laptop = new Product({
name: "Ноутбук Dell XPS 15",
price: 1499.99,
tags: ["электроника", "ноутбуки"]
});
await laptop.save();
console.log("Product saved:", laptop);
// READ (с фильтром)
const cheapProducts = await Product.find({ price: { $lt: 1000 } });
console.log("Cheap products:", cheapProducts);
// UPDATE (одно поле)
await Product.updateOne(
{ name: "Ноутбук Dell XPS 15" },
{ $set: { price: 1399.99 } }
);
// DELETE
await Product.deleteOne({ _id: laptop._id });
}
runMongo().catch(console.error);
Обратите внимание: Mongoose автоматически преобразует типы TypeScript в типы BSON, поддерживаемые MongoDB.
4. Сравнение подходов и лучшие практики
Выбор между SQL и NoSQL зависит от структуры данных. Вот несколько практических рекомендаций для TypeScript-разработчиков:
- Типизация — ваш друг. Всегда создавайте интерфейсы для документов MongoDB и используйте декораторы TypeORM. Это поможет избежать ошибок на этапе компиляции.
- Используйте DTO (Data Transfer Objects). Не передавайте сущности напрямую в HTTP-ответы. Создавайте отдельные типы для API-слоя.
- Обработка ошибок. Всегда оборачи