Які найкращі патерни інтеграції бази даних в Express.js?
Інтеграція бази даних в Express.js
Express.js не вказує, як взаємодіяти з базами даних. Вибір правильного шаблону залежить від складності та масштабу вашого застосунку.
1. Прямий запит (Простий)
js
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
app.get('/api/users', async (req, res, next) => {
try {
const { rows } = await pool.query('SELECT * FROM users LIMIT 50');
res.json(rows);
} catch (err) {
next(err);
}
});Плюси: Простий, прямий, без абстракції
Мінуси: SQL розкиданий по маршрутах, важко тестувати
2. Шаблон репозиторію
Абстрагуйте операції з базою даних у спеціалізовані класи:
js
// repositories/user.repository.js
class UserRepository {
constructor(pool) {
this.pool = pool;
}
async findAll({ limit = 50, offset = 0 } = {}) {
const { rows } = await this.pool.query(
'SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
return rows;
}
async findById(id) {
const { rows } = await this.pool.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return rows[0] || null;
}
async create(data) {
const { rows } = await this.pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[data.name, data.email]
);
return rows[0];
}
}
module.exports = UserRepository;js
// Використання в маршрутах
const userRepo = new UserRepository(pool);
app.get('/api/users', async (req, res, next) => {
try {
const users = await userRepo.findAll({ limit: req.query.limit });
res.json(users);
} catch (err) {
next(err);
}
});3. Шаблон ORM (Prisma / Sequelize / TypeORM)
js
// З Prisma
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
app.get('/api/users', async (req, res) => {
const users = await prisma.user.findMany({
include: { posts: true },
take: 50,
orderBy: { createdAt: 'desc' }
});
res.json(users);
});
app.post('/api/users', async (req, res) => {
const user = await prisma.user.create({
data: { name: req.body.name, email: req.body.email }
});
res.status(201).json(user);
});4. Шаблон сервісного шару
Додайте сервісний шар між маршрутами та репозиторіями:
js
// services/user.service.js
class UserService {
constructor(userRepo, emailService) {
this.userRepo = userRepo;
this.emailService = emailService;
}
async createUser(data) {
const existing = await this.userRepo.findByEmail(data.email);
if (existing) throw new ConflictError('Email вже існує');
const user = await this.userRepo.create(data);
await this.emailService.sendWelcome(user.email);
return user;
}
async getUsers(params) {
return this.userRepo.findAll(params);
}
}js
// маршрути
app.post('/api/users', async (req, res, next) => {
try {
const user = await userService.createUser(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
});Керування з'єднаннями
js
// Singleton pool — спільний для всього застосунку
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Максимум з'єднань у пулі
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000
});
// Коректне завершення роботи
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});Підтримка транзакцій
js
async function transferMoney(fromId, toId, amount) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
[amount, fromId]
);
await client.query(
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
[amount, toId]
);
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}Порівняння шаблонів
| Шаблон | Складність | Тестування | Гнучкість | Найкраще підходить для |
|---|---|---|---|---|
| Прямий запит | Низька | Важко | Низька | Прототипи |
| Репозиторій | Середня | Легко | Середня | Більшість застосунків |
| ORM | Середня | Легко | Висока | Швидка розробка |
| Сервіс + Репо | Висока | Найкраще | Найкраще | Великі застосунки |
Рекомендація: Розпочніть з шаблону репозиторію для чистого розділення. Додайте сервісний шар, коли бізнес-логіка зросте. Використовуйте Prisma або Drizzle для типобезпечного доступу до бази даних. Завжди використовуйте пул з'єднань і коректно обробляйте транзакції.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.