Что такое Express.js
Зачем нужно
Express -- минималистичный веб-фреймворк для Node.js. Он оборачивает встроенный модуль http удобным API: роутинг, middleware, обработка ошибок, раздача статики. Вместо ручного парсинга URL и метода в http.createServer Express даёт app.get('/users', handler). Это самый популярный фреймворк Node.js (~30M скачиваний в неделю).
Где используется
- REST API (бэкенд для SPA, мобильных приложений)
- Server-side rendering (с EJS, Pug, Handlebars)
- Микросервисы и BFF (Backend For Frontend)
- Прокси-серверы и API-гейтвеи
- Прототипирование и MVP
- Фундамент для Nest.js (под капотом Express или Fastify)
Предпосылки
- http — как работает HTTP-сервер в Node.js
- npm basics — установка пакетов
- package.json — структура проекта
Установка
# Создать проект
mkdir my-api && cd my-api
npm init -y
# Установить Express
npm install express
Hello World
const express = require('express');
const app = express;
// Обработка GET-запроса на /
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
// Запуск сервера
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running: http://localhost:${PORT}`);
});
node app.js
# Server running: http://localhost:3000
curl http://localhost:3000
# Hello, Express!
Сравнение: http vs Express
// === Чистый http ===
const http = require('http');
http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (req.method === 'GET' && url.pathname === '/api/users') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{ id: 1, name: 'Anna' }]));
} else if (req.method === 'POST' && url.pathname === '/api/users') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const user = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
});
} else {
res.writeHead(404);
res.end('Not Found');
}
}).listen(3000);
// === Express ===
const express = require('express');
const app = express;
app.use(express.json()); // парсинг JSON-тела
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Anna' }]);
});
app.post('/api/users', (req, res) => {
res.status(201).json(req.body);
});
app.listen(3000);
Express убирает повторяющийся код: парсинг URL, методов, тела запроса, Content-Type.
Объекты req и res в Express
req — расширенный request
app.get('/api/users/:id', (req, res) => {
// Параметры маршрута
console.log(req.params); // { id: '42' }
console.log(req.params.id); // '42'
// Query-параметры (?page=2&limit=10)
console.log(req.query); // { page: '2', limit: '10' }
console.log(req.query.page); // '2'
// Заголовки
console.log(req.headers); // { 'content-type': '...', ... }
console.log(req.get('Content-Type')); // 'application/json'
// Метод и путь
console.log(req.method); // 'GET'
console.log(req.path); // '/api/users/42'
console.log(req.originalUrl); // '/api/users/42?page=2'
// Тело запроса (после express.json() middleware)
console.log(req.body); // { name: 'Anna' } (для POST/PUT)
// IP клиента
console.log(req.ip); // '127.0.0.1'
});
res — расширенный response
app.get('/api/data', (req, res) => {
// JSON-ответ (автоматически Content-Type: application/json)
res.json({ status: 'ok', data: [1, 2, 3] });
// Текстовый ответ
res.send('Hello!');
// HTML
res.send('<h1>Hello</h1>');
// Установка статус-кода
res.status(201).json({ id: 1 });
res.status(404).json({ error: 'Not Found' });
res.status(204).end(); // No Content, без тела
// Редирект
res.redirect('/new-url');
res.redirect(301, '/permanent-new-url');
// Заголовки
res.set('X-Custom-Header', 'value');
res.set({
'X-Powered-By': 'My Server',
'Cache-Control': 'no-cache'
});
// Скачивание файла
res.download('/path/to/file.pdf');
// Отправка файла
res.sendFile('/absolute/path/to/file.html');
});
Концепция Middleware
Middleware -- функция, имеющая доступ к req, res и next. Каждый запрос проходит через цепочку middleware-ов, как через конвейер.
Запрос → [Middleware 1] → [Middleware 2] → [Route Handler] → Ответ
Каждый middleware может:
1. Изменить req/res
2. Завершить запрос (res.send)
3. Передать управление дальше (next)
const express = require('express');
const app = express;
// Middleware 1: логирование
app.use((req, res, next) => {
console.log(`${new Date.toISOString()} ${req.method} ${req.url}`);
next; // передать следующему middleware
});
// Middleware 2: парсинг JSON-тела
app.use(express.json());
// Middleware 3: CORS
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', '*');
next;
});
// Route handler (конечный обработчик)
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Anna' }]);
});
app.listen(3000);
Request-Response цикл
Клиент отправляет HTTP-запрос
│
▼
┌─────────────────────────┐
│ express.json() │ ← Парсит тело запроса
└────────┬────────────────┘
│ next
▼
┌─────────────────────────┐
│ Логирование │ ← Записывает запрос в лог
└────────┬────────────────┘
│ next
▼
┌─────────────────────────┐
│ Аутентификация │ ← Проверяет JWT-токен
└────────┬────────────────┘
│ next
▼
┌─────────────────────────┐
│ Route handler │ ← Бизнес-логика + res.json()
└────────┬────────────────┘
│
▼
Клиент получает HTTP-ответ
Если на любом шаге вызов next пропущен
и res.send/json не вызван — запрос "зависнет"
Структура проекта
my-api/
├── src/
│ ├── app.js ← создание Express-приложения
│ ├── server.js ← запуск сервера (app.listen)
│ ├── routes/
│ │ ├── users.js ← /api/users
│ │ └── posts.js ← /api/posts
│ ├── middleware/
│ │ ├── auth.js ← проверка аутентификации
│ │ └── errorHandler.js ← обработка ошибок
│ └── controllers/
│ ├── userController.js
│ └── postController.js
├── package.json
├── .env
└── .gitignore
// src/app.js
const express = require('express');
const cors = require('cors');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
const errorHandler = require('./middleware/errorHandler');
const app = express;
// Глобальные middleware
app.use(cors);
app.use(express.json());
// Маршруты
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
// Обработка ошибок (всегда последний middleware)
app.use(errorHandler);
module.exports = app;
// src/server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Полный пример: минимальный API
const express = require('express');
const app = express;
app.use(express.json());
// Хранилище (для примера — в памяти)
let todos = [
{ id: 1, text: 'Learn Express', done: false },
{ id: 2, text: 'Build API', done: false }
];
let nextId = 3;
// GET /api/todos — все задачи
app.get('/api/todos', (req, res) => {
res.json(todos);
});
// GET /api/todos/:id — одна задача
app.get('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === Number(req.params.id));
if (!todo) {
return res.status(404).json({ error: 'Todo not found' });
}
res.json(todo);
});
// POST /api/todos — создать задачу
app.post('/api/todos', (req, res) => {
const { text } = req.body;
if (!text) {
return res.status(400).json({ error: 'Text is required' });
}
const todo = { id: nextId++, text, done: false };
todos.push(todo);
res.status(201).json(todo);
});
// PATCH /api/todos/:id — обновить задачу
app.patch('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === Number(req.params.id));
if (!todo) {
return res.status(404).json({ error: 'Todo not found' });
}
Object.assign(todo, req.body);
res.json(todo);
});
// DELETE /api/todos/:id — удалить задачу
app.delete('/api/todos/:id', (req, res) => {
const index = todos.findIndex(t => t.id === Number(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Todo not found' });
}
todos.splice(index, 1);
res.status(204).end();
});
app.listen(3000, () => {
console.log('Todo API: http://localhost:3000/api/todos');
});
Частые ошибки
- Забывают
express.json—req.bodyбудетundefinedдля POST/PUT запросов - Нет
returnперед res.json() — код продолжает выполняться после ответа, вызывая двойной ответ - Забывают
nextв middleware — запрос зависает, клиент не получает ответ - Порядок middleware —
app.use(express.json())должен быть ДО route handler-ов - Обработка ошибок не последняя — error handler middleware должен быть определён после всех маршрутов
- Хардкод порта —
app.listen(3000)вместоprocess.env.PORT || 3000
Практика
- Создать Express-сервер с
app.get('/'), возвращающий JSON - Добавить
express.jsonи создать POST-эндпоинт, принимающий данные - Реализовать полный CRUD API для ресурса (GET, POST, PATCH, DELETE)
- Добавить middleware для логирования каждого запроса (метод, URL, время)
- Настроить переменную PORT через process.env
Связанные темы
- http — чистый HTTP-сервер (без Express)
- Роутинг — маршруты и параметры
- Middleware — цепочка middleware