Express.jsMiddleTechnical
Что такое паттерн MVC и как он применяется в Express?
MVC делит Express-приложение на Model (данные/БД), View (шаблоны или JSON), Controller (оркестрация). Роуты только регистрируют пути и вызывают методы контроллера.
Паттерн MVC в Express.js
MVC (Model–View–Controller) — архитектурный паттерн, разделяющий приложение на три слоя с чёткими обязанностями:
- Model — бизнес-логика и работа с данными (запросы к БД, валидация, трансформация).
- View — представление данных пользователю (HTML-шаблоны или JSON для API).
- Controller — оркестратор: принимает запрос, вызывает модели, передаёт данные во view.
Структура проекта
src/
models/
user.model.js # SQL/ORM-запросы
job.model.js
controllers/
user.controller.js # обработчики запросов
job.controller.js
routes/
user.routes.js # только маршрутизация
job.routes.js
views/ # шаблоны (или не нужны для REST API)
jobs/list.pug
services/ # опциональный слой бизнес-логики
job.service.js
app.js
Пример реализации
// models/job.model.js — работа с данными
import { pool } from '../db.js';
export const JobModel = {
async findAll({ limit = 20, offset = 0 } = {}) {
const { rows } = await pool.query(
'SELECT id, title, company, salary FROM jobs ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
return rows;
},
async findById(id) {
const { rows } = await pool.query(
'SELECT * FROM jobs WHERE id = $1',
[id]
);
return rows[0] ?? null;
},
async create({ title, company, salary, description }) {
const { rows } = await pool.query(
'INSERT INTO jobs (title, company, salary, description) VALUES ($1, $2, $3, $4) RETURNING *',
[title, company, salary, description]
);
return rows[0];
},
};
// controllers/job.controller.js — оркестрация
import { JobModel } from '../models/job.model.js';
export const JobController = {
async list(req, res, next) {
try {
const { page = 1, limit = 20 } = req.query;
const offset = (Number(page) - 1) * Number(limit);
const jobs = await JobModel.findAll({ limit: Number(limit), offset });
res.json({ data: jobs, page: Number(page) });
} catch (err) {
next(err);
}
},
async detail(req, res, next) {
try {
const job = await JobModel.findById(req.params.id);
if (!job) return res.status(404).json({ error: 'Job not found' });
res.json(job);
} catch (err) {
next(err);
}
},
async create(req, res, next) {
try {
const job = await JobModel.create(req.body);
res.status(201).json(job);
} catch (err) {
next(err);
}
},
};
// routes/job.routes.js — только маршрутизация
import { Router } from 'express';
import { JobController } from '../controllers/job.controller.js';
import { authenticate } from '../middleware/auth.js';
const router = Router();
router.get('/', JobController.list);
router.get('/:id', JobController.detail);
router.post('/', authenticate, JobController.create);
export default router;
// app.js
import jobsRouter from './routes/job.routes.js';
app.use('/api/jobs', jobsRouter);
Когда добавлять слой Service
Если бизнес-логика сложная (отправка email после создания вакансии, взаимодействие с несколькими моделями), её выносят в services/. Controller вызывает Service, а не Model напрямую. Это сохраняет контроллеры тонкими и упрощает тестирование.
Подводные камни
- Толстые контроллеры — если в контроллере появляется бизнес-логика (расчёты, агрегации), её нужно выносить в модели или сервисы.
- Прямые SQL-запросы в контроллерах — нарушает MVC: модель должна владеть всеми операциями с данными.
- Смешение слоёв маршрутизации и контроллеров — писать обработчик прямо в routes-файле удобно для прототипа, но создаёт неподдерживаемый код.
- Отсутствие обработки ошибок в контроллере — каждый async-метод должен оборачивать await в try/catch и вызывать
next(err). - Жёсткая связь модели с конкретной СУБД — лучше абстрагировать интерфейс модели, чтобы легко подменять источник данных в тестах.
- View как HTML-шаблон vs JSON — в REST API роль View играет
res.json(); не нужно добавлять шаблонизатор только ради MVC.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API Express.js.
- Не объясняет, где в lifecycle находится MVC в Express.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить MVC в Express на примере кода.
- Называет ключевые API: controller, service, model.
- Использует точные API Express.js, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.