13. Request и Response объекты

req и res — два главных объекта в каждом роуте Express. Полный разбор всех свойств и методов.
Request (req) — полный разбор
Заголовок раздела «Request (req) — полный разбор»app.use((req, res, next) => { // === ТЕЛО ЗАПРОСА === req.body // { name: 'Яша' } — парсится middleware express.json()
// === URL ПАРАМЕТРЫ === req.params // { id: '42', slug: 'hello-world' } req.params.id // '42' (всегда строка!)
// === QUERY СТРОКА === req.query // { page: '1', limit: '10', tags: ['js', 'node'] } req.query.page // '1' (тоже строка!)
// === ЗАГОЛОВКИ === req.headers // объект всех заголовков req.headers['content-type'] // 'application/json' req.headers['authorization'] // 'Bearer token...' req.get('Authorization') // то же, удобнее req.header('Authorization') // синоним get()
// === МЕТОД И URL === req.method // 'GET', 'POST', 'PUT', 'DELETE' req.url // '/api/users?page=1' req.originalUrl // '/api/users?page=1' (после монтирования роутера) req.path // '/api/users' (без query) req.baseUrl // '/api' (префикс роутера) req.hostname // 'localhost' или 'example.com' req.protocol // 'http' или 'https' req.secure // true если https req.ip // '127.0.0.1' req.ips // ['127.0.0.1'] (при X-Forwarded-For)
// === COOKIES === // require cookie-parser middleware req.cookies // { token: 'abc', lang: 'ru' } req.signedCookies // подписанные куки
// === ПОЛЕЗНЫЕ ПРОВЕРКИ === req.is('application/json') // true/false — проверка Content-Type req.accepts('json') // 'json' или false req.fresh // true если ответ не изменился (кэш) req.stale // !req.fresh req.xhr // true если Ajax запрос (X-Requested-With: XMLHttpRequest)
next();});Response (res) — полный разбор
Заголовок раздела «Response (res) — полный разбор»app.get('/response-demo', (req, res) => { // === ОТПРАВКА ДАННЫХ === res.json({ data: 'value' }); // JSON ответ res.send('text'); // текст/HTML res.send(Buffer.from('binary')); // бинарные данные res.sendStatus(204); // только статус, без тела res.end(); // завершить без данных
// === СТАТУС-КОД === res.status(404).json({ error: 'Not found' }); res.status(201).json({ id: 1 });
// === ЗАГОЛОВКИ === res.set('Content-Type', 'application/pdf'); res.set({ 'X-A': '1', 'X-B': '2' }); res.setHeader('X-Custom', 'value'); // синоним set() res.get('Content-Type'); // получить заголовок res.removeHeader('X-Powered-By'); // удалить заголовок res.append('Link', '<https://example.com>; rel="preconnect"');
// === ПЕРЕНАПРАВЛЕНИЯ === res.redirect('/new-url'); res.redirect(301, '/permanent'); res.redirect('back'); // на предыдущую страницу
// === ФАЙЛЫ === res.sendFile('/absolute/path/file.pdf'); res.sendFile('report.pdf', { root: __dirname }); res.download('/path/to/file.xlsx', 'Отчёт 2024.xlsx');
// === COOKIES === res.cookie('token', 'value', { httpOnly: true, // недоступен из JS secure: true, // только HTTPS sameSite: 'strict', // защита CSRF maxAge: 7 * 24 * 60 * 60 * 1000, // 7 дней domain: '.example.com', // для поддоменов path: '/', }); res.clearCookie('token');
// === РЕНДЕРИНГ ШАБЛОНОВ === res.render('index', { title: 'Главная', user: req.user });
// === СОСТОЯНИЕ === res.headersSent // true если заголовки уже отправлены res.finished // true если ответ завершён});Паттерны ответов
Заголовок раздела «Паттерны ответов»// Фабрика ответов — единый форматclass ApiResponse { static success(res, data, status = 200) { return res.status(status).json({ success: true, data, timestamp: new Date().toISOString(), }); }
static created(res, data) { return this.success(res, data, 201); }
static noContent(res) { return res.sendStatus(204); }
static error(res, message, status = 500, details = null) { const body = { success: false, error: { message }, timestamp: new Date().toISOString(), }; if (details) body.error.details = details; return res.status(status).json(body); }
static notFound(res, resource = 'Resource') { return this.error(res, `${resource} not found`, 404); }
static unauthorized(res) { return this.error(res, 'Unauthorized', 401); }
static forbidden(res) { return this.error(res, 'Forbidden', 403); }
static validationError(res, details) { return this.error(res, 'Validation error', 400, details); }}
// Использованиеapp.get('/users/:id', async (req, res, next) => { try { const user = await db.findUser(req.params.id); if (!user) return ApiResponse.notFound(res, 'User'); ApiResponse.success(res, user); } catch (err) { next(err); }});Потоковая передача ответа
Заголовок раздела «Потоковая передача ответа»const { createReadStream } = require('fs');
// Стримим файл в ответapp.get('/download/:filename', (req, res) => { const filename = req.params.filename; const filepath = `./uploads/${filename}`;
res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
const stream = createReadStream(filepath); stream.on('error', (err) => { if (!res.headersSent) { res.status(404).json({ error: 'File not found' }); } }); stream.pipe(res);});
// Server-Sent Events (SSE)app.get('/events', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive');
let counter = 0; const interval = setInterval(() => { res.write(`data: ${JSON.stringify({ count: counter++ })}\n\n`); }, 1000);
req.on('close', () => { clearInterval(interval); });});Практика
Заголовок раздела «Практика»- Создай middleware, который добавляет
req.requestId(UUID) к каждому запросу - Напиши
ApiResponseкласс с методамиsuccess,error,notFound,paginated - Реализуй скачивание файла через
res.download()с проверкой прав - Создай эндпоинт SSE
/events, который каждую секунду отправляет серверное время - Добавь глобальный middleware для логирования: метод, URL, статус, время ответа