Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як побудувати REST API з Express.js?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**REST API з Express.js** призначає HTTP-методи обробникам для CRUD-операцій над ресурсами. ```js app.use(express.json()); app.get('/users', (req, res) => res.json(users)); app.post('/users', (req, res) => { const user = { id: nextId++, ...req.body }; users.push(user); res.status(201).json(user); }); ``` **Головне:** `express.json()` реєструй до маршрутів, що читають `req.body`, інакше тіло запиту буде `undefined`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.