Skip to main content

Як побудувати REST API з Express.js?

REST API з Express.js - сервер, який призначає кожному HTTP-методу і шляху обробник, що читає, створює, оновлює або видаляє ресурс.

Теорія

TL;DR

  • app.use(express.json()) потрібно додати до маршрутів, що читають req.body, інакше req.body буде undefined
  • GET читає, POST створює, PUT замінює цілий об'єкт, PATCH оновлює окремі поля, DELETE видаляє
  • Коди статусу - частина контракту: 201 для створення, 204 для видалення (без тіла), 404 якщо не знайдено, 400 при невалідних даних
  • Параметри маршруту в req.params, рядок запиту в req.query, тіло запиту в req.body
  • Імена ресурсів у URL мають бути іменниками у множині: /users, не /user і не /getUsers

Мінімальний робочий приклад

js
const express = require('express'); const app = express(); app.use(express.json()); // без цього req.body завжди undefined app.get('/users', (req, res) => { res.json([{ id: 1, name: 'Alice' }]); // повертає масив JSON }); app.post('/users', (req, res) => { const { name } = req.body; // читає розпарсений JSON res.status(201).json({ id: 2, name }); // 201 = Created }); app.listen(3000);

express.json() парсить тіло запиту як JSON і кладе результат у req.body. Без цього рядка обробники POST і PUT не отримують нічого.

HTTP-методи і CRUD

HTTP-методCRUDКоли використовувати
GETЧитатиОтримати ресурс або список
POSTСтворитиСтворити новий ресурс
PUTОновити (повністю)Замінити весь об'єкт
PATCHОновити (частково)Змінити одне або кілька полів
DELETEВидалитиВидалити ресурс

PUT замінює весь об'єкт. Якщо ти PUT-нув { name: 'Alice' } до користувача, у якого був ще й email, email зникне. PATCH чіпає лише те, що ти надіслав. Більшість реальних API використовують PATCH для оновлень, бо клієнту рідко потрібно надсилати незмінені дані повторно.

Читання даних запиту

js
// GET /users/42?format=json app.get('/users/:id', (req, res) => { req.params.id // '42' — завжди рядок req.query.format // 'json' req.headers['authorization'] // Bearer-токен req.method // 'GET' req.path // '/users/42' }); // POST /users з JSON-тілом app.post('/users', (req, res) => { req.body.name // з розпарсеного JSON req.body.email });

req.params.id завжди рядок. Порівнювати його з числовим ID через === не вийде: вони ніколи не збіжаться. Конвертуй через Number(req.params.id) перед пошуком.

Надсилання відповідей

js
res.json({ data }) // надсилає JSON, Content-Type встановлюється автоматично res.status(201).json(data) // статус + тіло по ланцюжку res.status(204).send() // без тіла — для успішного DELETE res.status(404).json({ error: 'Не знайдено' }) res.redirect('/new-path')

Express за замовчуванням повертає 200. Клієнт, що отримав 200 на POST, не знає, чи ресурс справді створили. Встановлюй код статусу явно.

Правила проектування

  1. Іменники в URL, не дієслова: /users, не /getUsers або /deleteUser
  2. Множина для назв ресурсів: /users, не /user
  3. Версіонування з першого дня: /api/v1/users
  4. Єдина структура JSON: однаковий формат для успіху і помилки в кожній відповіді
  5. HTTP-методи несуть дію, не шляхи URL

Версіонування важливіше, ніж здається. Коли ти задеплоїв /api/users і клієнт почав ним користуватись, змінити структуру відповіді вже не вийде без зламу. /api/v2/users дає змогу розвивати API, поки v1 залишається стабільним.

Типові помилки

Відсутній express.json()

js
// Неправильно — req.body undefined, помилки немає app.post('/users', (req, res) => { const user = { ...req.body }; // {} — порожній об'єкт, дані втрачено users.push(user); res.status(201).json(user); }); // Правильно app.use(express.json()); // додай перед визначенням маршрутів

Неправильні коди статусу

js
// Неправильно — 200 при створенні, 200 при видаленні app.post('/users', (req, res) => res.json(user)); app.delete('/users/:id', (req, res) => res.json({ ok: true })); // Правильно app.post('/users', (req, res) => res.status(201).json(user)); app.delete('/users/:id', (req, res) => res.status(204).send());

Забути конвертувати req.params.id

js
// Неправильно — рядок '42' ніколи не дорівнює числу 42 const user = users.find(u => u.id === req.params.id); // Правильно const user = users.find(u => u.id === Number(req.params.id));

Дієслова в URL

js
// Неправильно app.post('/createUser', ...); app.get('/deleteUser/:id', ...); // Правильно app.post('/users', ...); app.delete('/users/:id', ...);

Де зустрічається на практиці

  • Express - HTTP-шар у багатьох Node.js-бекендах, часто разом з ORM для бази даних
  • В реальних проектах маршрути розбивають по файлах через express.Router()
  • express.urlencoded() обробляє HTML-форми, express.json() - API-клієнти
  • Валідація відбувається до звернення до бази: перевіряй обов'язкові поля або використовуй бібліотеки типу zod чи joi

Питання на співбесіді

Q: Яка різниця між PUT і PATCH?
A: PUT замінює весь об'єкт. Поля, які не надіслав, зникають. PATCH оновлює лише ті поля, що є в тілі запиту. Більшість команд використовують PATCH для оновлень, щоб не видалити дані випадково.

Q: Чому повертати 204 замість 200 при DELETE?
A: 204 означає успіх без тіла відповіді. Ресурс більше не існує, повертати нічого. 200 з порожнім тілом теж спрацює, але 204 - прийнята конвенція.

Q: Що відбувається, якщо два маршрути збігаються з одним шляхом?
A: Express виконає перший збіг і зупиниться. Другий не запуститься, якщо перший обробник не викличе next(). Це важливо при додаванні middleware поверх обробників маршрутів.

Q: Як розбити маршрути по файлах при зростанні додатка?
A: Використовуй express.Router(). Визнач маршрути на екземплярі роутера, експортуй його, потім app.use('/users', usersRouter) у головному файлі. Роутер працює з '/' і '/:id', префікс задається через app.use.

Q: Як захистити маршрут для автентифікованих користувачів?
A: Напиши middleware-функцію, що перевіряє заголовок Authorization, валідує токен і або викликає next(), або повертає res.status(401).json({ error: 'Unauthorized' }). Додай її перед обробником: app.get('/users', authMiddleware, handler).

Приклади

Повний CRUD для ресурсу users

js
const express = require('express'); const app = express(); app.use(express.json()); let users = [ { id: 1, name: 'Alice', email: 'alice@example.com' }, { id: 2, name: 'Bob', email: 'bob@example.com' }, ]; let nextId = 3; // GET /users app.get('/users', (req, res) => { res.json(users); }); // GET /users/:id app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === Number(req.params.id)); if (!user) return res.status(404).json({ error: 'Користувача не знайдено' }); res.json(user); }); // POST /users app.post('/users', (req, res) => { const { name, email } = req.body; if (!name || !email) { return res.status(400).json({ error: 'name та email є обовязковими' }); } const user = { id: nextId++, name, email }; users.push(user); res.status(201).json(user); }); // PUT /users/:id app.put('/users/:id', (req, res) => { const index = users.findIndex(u => u.id === Number(req.params.id)); if (index === -1) return res.status(404).json({ error: 'Користувача не знайдено' }); users[index] = { id: Number(req.params.id), ...req.body }; res.json(users[index]); }); // PATCH /users/:id app.patch('/users/:id', (req, res) => { const user = users.find(u => u.id === Number(req.params.id)); if (!user) return res.status(404).json({ error: 'Користувача не знайдено' }); Object.assign(user, req.body); res.json(user); }); // DELETE /users/:id app.delete('/users/:id', (req, res) => { const index = users.findIndex(u => u.id === Number(req.params.id)); if (index === -1) return res.status(404).json({ error: 'Користувача не знайдено' }); users.splice(index, 1); res.status(204).send(); }); app.listen(3000, () => console.log('API працює на порту 3000'));

Кожен обробник повертає раннє 404 при відсутності ресурсу. Це тримає основний шлях плоским і позбавляє від вкладених if. Варто зробити це стандартом з першого маршруту.

Єдина структура відповіді

js
// Успіх res.json({ success: true, data: user }); // Помилка валідації res.status(400).json({ success: false, error: 'name та email є обовязковими' }); // Список з пагінацією res.json({ success: true, data: users, pagination: { page: 1, limit: 10, total: 100 } });

Клієнт перевіряє success, потім читає data або error. Єдиний формат означає одне місце в клієнтському коді для обробки всіх відповідей.

Розбивка маршрутів через express.Router

js
// routes/users.js const router = require('express').Router(); router.get('/', (req, res) => res.json(users)); router.get('/:id', (req, res) => { /* один користувач */ }); router.post('/', (req, res) => { /* створення */ }); module.exports = router; // app.js const usersRouter = require('./routes/users'); app.use('/users', usersRouter); // GET /users -> router.get('/') // GET /users/1 -> router.get('/:id')

Файл роутера не знає свого префіксу. app.use('/users', usersRouter) задає його при підключенні. Тому всередині роутера пишеш / і /:id, а не /users і /users/:id.

Коротка відповідь

Для співбесіди
Premium

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

Дочитали статтю?