Сім початкових завдань з програмування на Node.js та Express, розв’язаних та пояснених
Виконайте сім типових завдань з інтерв’ю для початківців з Node.js: від рут Express та мідлвейру до обробки помилок, зворотного перебирання рядків та виявлення дублікатів, і навчіться пояснювати кожне з них.
На співбесідах з Junior Node.js та Express рідко йдеться про складні алгоритми. Зазвичай вам пропонують короткі завдання, такі як створення маршруту, написання middleware чи очищення масиву, під час чого співбесідник спостерігає, як ви міркуєте та чи можете пояснити, чому ваш код працює. Сім наведених нижче завдань охоплюють найпоширеніші ситуації; кожне з них містить готове рішення, пояснення ключових рядків та додаткові питання, які перевіряють справжнє розуміння теми.
Завдання 1: Створіть базовий маршрут GET
Класичне вступне завдання: «Створіть GET-кінцеву точку за адресою /, яка повертатиме текст Hello World.» Рішення полягає у створенні застосунку Express, реєстрації обробника для кореневого шляху та початку прослуховування на певному порту.
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.status(200).send("Hello World");
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
Оскільки використовується import, проект потребує значення "type": "module" у файлі package.json або розширення .mjs.
Читання маршруту рядок за рядком
Виклик app.get() реєструє обробник, який реагує лише на запити типу GET:
app.get("/", (req, res) => {
Перший аргумент, /, — це шлях. Функція-колбек отримує req, який описує надісланий запит, та res, який використовується для створення відповіді.
Сама відповідь генерується тут:
res.status(200).send("Hello World");
status(200) встановлює код статусу (200 — це значення за замовчуванням, але його чітке вказування полегшує розуміння), а send() записує тіло відповіді та завершує її.
Наостанок запускається сервер:
app.listen(3000, () => {
Це прив’язує додаток до порту 3000; калебек викликається після того, як сервер приймає з’єднання. У справжніх сервісах порт зазвичай читається з process.env.PORT.
Як це сказати
"
app.get()реєструє маршрут для запитів типу GET. Коли клієнт викликає/, Express запускає калебек, який відповідає з кодом статусу 200 та текстом Hello World."
Завдання 2: Написати проміжок для логування запитів
Наступне завдання: "Створіть проміжок, який буде логувати кожен надходячий запит." Проміжок — це функція з сигнатурою (req, res, next). Вона виводить метод та URL, а потім передає керування далі; app.use() застосовує її до кожного маршруту, зареєстрованого після неї.
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
app.use(logger);
Кожен запит проходить через ланцюг у порядку, перш ніж обробник маршруту відповість:
Client
↓
Request
↓
Logger Middleware
↓
Route Handler
↓
Response
Найважливішим рядком є останній усередині функції:
next();
next() повідомляє Express, що цей крок завершено та має бути виконаний наступний середовий модуль чи маршрут. Якщо цього не зробити, запит залишається у стані очікування до тих пір, поки клієнт не вичекає таймауту — це поширене запитання. Альтернативою є завершення запиту шляхом надсилання відповіді, як це робить середовий модуль автентифікації, коли відхиляє відсутній токен.
Як це сказати
"Середовий модуль отримує запит, відповідь та
next. Він може перевірити чи змінити запит, а потім або надати відповідь, або викликатиnext(), щоб передати керування далі."
Завдання 3: Обробка асинхронних операцій у маршруті
Реальні кінцеві точки чекають на дані з баз даних та інших API, тож можна очікувати запитання „Як обробляти асинхронні операції в Express?“ Хорошою відповіддю є обробник типу async із блоками try/catch:
app.get("/users", async (req, res, next) => {
try {
const users = await getUsers();
res.status(200).json(users);
} catch (error) {
next(error);
}
});
Ключовою лінією є await:
const users = await getUsers();
Він зупиняє виконання цього обробника до тих пір, поки getUsers() не завершиться, не блокуючи при цьому інші запити. Якщо обіцянка відхилиться, виконується блок catch:
catch (error) {
next(error);
}
next(error) передає проблему мідлвейру обробки помилок з Завдання 5. Варто додати, що Express 4 не фіксує відхилених обіцянок від асинхронних обробників, тому тут try/catch є обов’язковим, тоді як Express 5 автоматично їх передає. Для більш детального ознайомлення дивіться створення обробки помилок промислового рівня в додатках Node.js.
Завдання 4: Скетчування кінцевих точок CRUD
CRUD означає Create, Read, Update та Delete — чотири основні операції з даними, кожна з яких відповідає певному методу HTTP. Шаблони для ресурсу users:
app.post("/users", (req, res) => {
res.status(201).send("User created");
});
app.get("/users", (req, res) => {
res.status(200).send("Users");
});
app.put("/users/:id", (req, res) => {
res.status(200).send("User updated");
});
app.delete("/users/:id", (req, res) => {
res.status(200).send("User deleted");
});
Створення повертає код 201 Created, а маршрути для одного користувача приймають параметр :id, який береться з req.params.id. Замість того, щоб запам’ятовувати коди, потрібно знати значення кожного методу:
POST → Create
GET → Read
PUT → Update
DELETE → Delete
Готуйтесь до різниці між PUT та PATCH: PUT зазвичай замінює весь ресурс, а PATCH застосовує часткові зміни.
Як це сказати
"CRUD охоплює чотири основні операції з даними. У REST POST використовується для створення, GET — для читання, PUT або PATCH — для оновлення, а DELETE — для видалення."
Завдання 5: Централізація обробки помилок
Обробка помилок зазвичай виконується після завдання 3. Почніть з маршруту, який призводить до помилки, та перенаправте її:
app.get("/", (req, res, next) => {
try {
throw new Error("Something went wrong");
} catch (error) {
next(error);
}
});
Потім додайте обробник помилок, який записує інформацію та повертає загальний код помилки 500:
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({
message: "Internal Server Error"
});
});
Те, що робить цю функцію особливою, — це її сигнатура:
(err, req, res, next)
Express розпізнає обробники помилок за чотирма оголошеними параметрами у порядку err, req, res, next; вони виконуються лише тоді, коли передано помилку. Зареєструйте обробник після своїх маршрутів та уникайте надсилання err.message до клієнтів у продакшен-середовищі. Послідовність дій:
Route
↓
Error occurs
↓
catch(error)
↓
next(error)
↓
Error middleware
↓
500 Response
Чи є необхідним використання next() усередині обробника помилок?
Ні, якщо обробник сам надсилає відповідь:
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).send("Internal Server Error");
});
next все одно має бути оголошений, тому що при наявності трьох параметрів Express розглядає функцію як звичайний мідлвейр.
Завдання 6: Перевернути рядок
Деякі запитання стосуються простого JavaScript: «Перевернути рядок hello.» Ідіоматичний однорядковий код поєднує три методи:
const str = "hello";
const reversed = str.split("").reverse().join("");
console.log(reversed);
Він виводить:
olleh
Що робить кожен крок
Функція split розділяє рядок на символи:
str.split("")
Результатом є масив:
["h", "e", "l", "l", "o"]
Потім порядок елементів масиву змінюється на місці:
.reverse()
Це дає:
["o", "l", "l", "e", "h"]
Нарешті символи об’єднуються без жодного розділювача:
.join("")
Це створює перевернутий рядок:
"olleh"
Поширена зміна умови: «Тепер зробіть це без reverse().» Йдемо по колу від останнього індексу до нуля:
const str = "hello";
let reversed = "";
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
console.log(reversed);
Обидва підходи є лінійними. Варто зазначити, що split("") та індексування працюють з кодовими одиницями UTF-16, тому емодзі розбиваються на частини; [...str] натомість розділяє рядок за кодовими точками.
Завдання 7: Знайти дублікати в масиві
Ще одне улюблене завдання: «Повернути значення, які зустрічаються більше одного разу». Дано:
const numbers = [1, 2, 3, 2, 4, 3, 5];
Очікуваний результат є:
[2, 3]
Використовуйте два об’єкти Set: один для значень, які вже були побачені, та інший для підтверджених дублікатів:
const numbers = [1, 2, 3, 2, 4, 3, 5];
const seen = new Set();
const duplicates = new Set();
for (const number of numbers) {
if (seen.has(number)) {
duplicates.add(number);
} else {
seen.add(number);
}
}
console.log([...duplicates]);
Під час виконання формується журнал:
[2, 3]
Чому працюють два множини
seen фіксує кожне передане значення. Перша зустріч додається сюди:
seen.add(number);
Повторна зустріч призводить до позитивного результату цієї перевірки:
if (seen.has(number))
Потім значення потрапляє до множини duplicates, і оскільки Set ігнорує повтори, про нього повідомляється лише один раз. Пошук у множині відбувається майже за константний час, тому рішення є лінійним, а не квадратичним через вкладені цикли. Щоб підрахувати кількість зустрічей, використовуйте Map.
Обміркуйте свій підхід перед тим, як почати писати
Найважливішою звичкою є пояснення свого плану перед написанням коду, адже співрозмовники оцінюють логіку так само, як і результати. Для завдання з дублікатами почніть з чогось на кшталт:
"План — це множина вже бачених значень. Під час циклу кожне значення, яке вже є в ній, потрапляє до другої множини дублікатів, що зберігає унікальність кожного з них."
Потім пишіть код. Це доводить розуміння матеріалу замість запам’ятовування та дозволяє співрозмовнику рано скерувати вас у правильному напрямку.
Чек-лист підготовки
Перед співбесідою з початківцями з Node.js чи Express переконайтеся, що ви можете легко:
- Визначати маршрути та пояснювати функції
reqтаres - Використовувати
app.use()та писати власні проміжки обробки - Пояснювати, що робить
next()та що відбувається, якщо його немає
async/await, try/catch та next(error)Set та Map для пошуку та підрахункуНа цьому рівні міцні основи, зрозумілий код та чіткі пояснення мають набагато більше значення, ніж складні алгоритми.
Пов’язана література
- Node.js 26: Temporal API, Map Upserts та Undici 8 — пояснення — Пояснює основні зміни в Node.js 26, спрямовані на роботу з бекендом, включаючи стабільний Temporal API, вбудовані методи Map upsert, покращення продуктивності Undici 8 та зміни, які можуть спричинити проблеми під час оновлення, що потребують перевірки.
- Node.js Streams — пояснення: як усунути збої через нестачу пам’яті при роботі з файлами — Дізнайтеся, чому завантаження всіх файлів у пам’ять призводить до збоїв серверів Node.js, та як потоки типу readable, writable, duplex та transform допомагають вирішити цю проблему за допомогою механізму backpressure.