Skip to main content
Практика завдань

Які найкращі патерни інтеграції бази даних в 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

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань