Коротка відповідь. Node.js-співбесіда майже завжди складається з трьох шарів: як працює сам Node (event loop, асинхронність), як ти будуєш HTTP-сервіси (маршрути, middleware, обробка помилок) і як працюєш із даними (база, транзакції, кеш). Готуйся до кожного шару окремо: спочатку впевнене пояснення, потім робочий код. Нижче – маршрут і типові запитання з відповідями.

Що реально перевіряють

Інтерв'юер на backend-позицію хоче зрозуміти три речі:

  1. Чи розумієш ти модель виконання Node.js. Один потік, event loop, неблокуючий I/O. Без цього решта відповідей – завчені слова.
  2. Чи вмієш ти будувати надійні API. Валідація вхідних даних, помилки, статуси, структура коду.
  3. Чи розумієш ти дані. Коли транзакція обов'язкова, чому N+1 запитів – проблема, що кешувати.

Шар 1 – модель виконання

Мінімум, який треба пояснювати без запинки:

  • Event loop. Node виконує JavaScript в одному потоці; довгі операції (мережа, диск) віддаються системі, а колбеки повертаються в чергу. Тому сервер обробляє тисячі з'єднань без потоку на кожне.
  • Блокування. Синхронний код (JSON.parse величезного файла, важкий цикл) зупиняє ВЕСЬ сервер, а не один запит. Це найчастіша пастка в запитаннях «чому сервіс завис».
  • Microtasks vs macrotasks. Promise.then виконується раніше за setTimeout(fn, 0). Загальна механіка та сама, що в браузері – детальний розбір є в статті про Event Loop у JavaScript.
  • Streams – на рівні ідеї. Читати файл на 2 ГБ через fs.readFile означає покласти 2 ГБ у пам'ять; stream обробляє дані шматками. Достатньо вміти пояснити навіщо і навести приклад pipe з файла у відповідь HTTP.
  • Модулі. Різниця CommonJS (require) і ESM (import), і чому в сучасних проєктах ESM. Першоджерело: офіційні гайди Node.js.

Шар 2 – HTTP-сервіси

Middleware в Express

Middleware – це функції, через які проходить запит до того, як дійде до обробника. Логування, auth, парсинг тіла – усе це ланцюжок:

import express from 'express';

const app = express();

app.use(express.json());

// Простий middleware авторизації
app.use((req, res, next) => {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
});

На співбесіді питають не «що таке middleware», а «в якому порядку вони виконуються» (у порядку реєстрації) і «що станеться, якщо забути next()» (запит зависне).

Nest на рівні концепцій

Якщо вакансія на Nest, треба розуміти dependency injection: клас оголошує залежності в конструкторі, фреймворк їх створює і підставляє. Це спрощує тестування – замість реального сервісу підставляєш mock:

@Injectable()
export class OrdersService {
  constructor(private readonly repository: OrdersRepository) {}

  findByUser(userId: string) {
    return this.repository.findMany({ userId });
  }
}

Достатньо пояснити навіщо DI (слабка зв'язаність, тестованість), не переказуючи документацію.

Шар 3 – дані

  • Транзакції. Коли дві операції мають виконатися разом або не виконатися взагалі (списати кошти + створити замовлення), потрібна транзакція. Вміти навести приклад, де без неї дані розсипаються.
  • N+1. Отримати 100 замовлень одним запитом, а потім по одному запиту на кожного покупця – це 101 запит. Рішення: JOIN або batch-завантаження.
  • Вибір бази. Мати аргументовану позицію, а не «Mongo, бо я його знаю». Розгорнутий розбір: SQL vs NoSQL: як пояснити вибір на співбесіді.
  • Кеш. Коли варто, а коли ні: Redis і кешування.

Типові запитання з відповідями

Чому Node.js однопотоковий, але обробляє багато запитів одночасно? JavaScript виконується в одному потоці, але I/O-операції делегуються системі через libuv. Поки база відповідає на запит A, Node обробляє запит B. Паралельний тут I/O, а не JavaScript.

Що станеться, якщо в обробнику виконати важке синхронне обчислення? Заблокується event loop: усі інші запити чекатимуть. Рішення: worker threads, черга задач або винесення обчислення в окремий сервіс.

Чим process.nextTick відрізняється від setImmediate? nextTick виконується до наступної фази event loop (раніше за проміси), setImmediate – в окремій фазі після I/O. Практичне правило: обидва рідко потрібні в прикладному коді, і вміння сказати це вголос теж цінується.

Як обробляти помилки в асинхронному коді? try/catch навколо await, централізований error-middleware в Express, обов'язковий обробник unhandledRejection на рівні процесу. Помилка без обробки в промісі може покласти процес.

Навіщо змінні середовища, а не конфіг у коді? Секрети не потрапляють у git, конфігурація змінюється між середовищами без перезбирання. Питання-маркер: перевіряють гігієну, а не знання.

Що таке ідемпотентність і чому вона важлива для API? Повторний виклик дає той самий результат. PUT ідемпотентний, POST зазвичай ні. Важливо для retry: клієнт може безпечно повторити запит після таймаута.

Як би ти зробив rate limiting? На рівні ідеї: лічильник запитів на ключ (IP/користувач) з вікном часу, зберігання в Redis, відповідь 429 при перевищенні. Плюс згадати, що на практиці часто це робить API gateway.

Live coding: endpoint з валідацією і помилками

Типова задача рівня Junior/Middle: «зроби POST /orders». Сильне рішення відрізняється не фічами, а охайністю країв:

import express from 'express';

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

type CreateOrderBody = {
  productId: string;
  quantity: number;
};

function validateOrder(body: unknown): CreateOrderBody | null {
  if (typeof body !== 'object' || body === null) return null;
  const { productId, quantity } = body as Record<string, unknown>;
  if (typeof productId !== 'string' || productId.length === 0) return null;
  if (typeof quantity !== 'number' || !Number.isInteger(quantity) || quantity < 1) {
    return null;
  }
  return { productId, quantity };
}

app.post('/orders', async (req, res, next) => {
  try {
    const order = validateOrder(req.body);
    if (!order) {
      return res.status(400).json({ error: 'productId і quantity (ціле ≥ 1) обов’язкові' });
    }
    const created = await ordersService.create(order);
    res.status(201).json(created);
  } catch (error) {
    next(error);
  }
});

// Централізований обробник помилок – останнім
app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
  console.error(error);
  res.status(500).json({ error: 'Internal server error' });
});

Коментуй уголос: чому 400 vs 500, чому валідація до сервісу, що б ти покрив тестами (валідатор і обидві гілки обробника).

Чекліст за рівнями

Junior:

  • пояснити event loop і навести приклад блокування;
  • зробити CRUD-endpoint з валідацією і статусами;
  • знати різницю GET/POST/PUT/DELETE і коди 200/201/400/401/404/500;
  • базовий SQL: SELECT з JOIN, INSERT.

Middle:

  • усе вище + транзакції і рівні ізоляції на рівні розуміння;
  • аргументований вибір бази і кешу;
  • структура застосунку: шари, DI, тестованість;
  • retry, ідемпотентність, rate limiting, graceful shutdown.

Типові помилки кандидатів

  1. Завчений event loop без застосування. Кандидат переказує фази циклу, але не може відповісти, чому конкретний endpoint «підвішує» сервер. Тренуй пояснення на прикладах, а не на схемах.
  2. async без обробки помилок. Код на співбесіді без жодного try/catch і без error-middleware читається як «у production це впаде». Обробка помилок – не бонус, а базова частина рішення.
  3. Статуси навмання. 200 на створення, 500 на невалідне тіло. Матриця «яка ситуація – який код» вчиться за вечір і сильно впливає на враження.
  4. «Я просто використовую ORM». ORM – нормально, але за ним треба бачити SQL: що таке індекс, чому запит у циклі – проблема, коли транзакція обов'язкова.
  5. Мовчазне кодування. Backend-задачі повні неозвучених рішень (валідація, помилки, межі). Якщо не коментуєш хід думок, інтерв'юер бачить лише фінальний код, а оцінює він мислення.

Практичне завдання

Візьми свій pet-проєкт або зроби новий endpoint за прикладом вище і доведи його до «співбесідного» стану: валідація, коректні статуси, централізовані помилки, один тест на happy path і один на невалідний вхід. Потім поясни рішення вголос за 5 хвилин, ніби поруч сидить інтерв'юер. Ця вправа дає більше, ніж ще один перегляд списку питань.

Перед реальною співбесідою пройди хоча б одну пробну: формат сильно відрізняється від самостійного розв'язування. Як це працює – у статті про mock interview.