JS Tower
Node.jsМодуль 4: HTTP и Express

Middleware

Встроенные, сторонние и кастомные middleware

Цель урока

В этом уроке ты научишься:

  • Понимать концепцию middleware
  • Использовать встроенные и сторонние middleware
  • Создавать собственные middleware

Что такое Middleware

Middleware — функция, которая имеет доступ к req, res и next. Выполняется между получением запроса и отправкой ответа.

function middleware(req, res, next) {
  // Логика
  next();  // Передать управление следующему middleware
}

Порядок выполнения

const app = express();

app.use((req, res, next) => {
  console.log('1. Первый middleware');
  next();
});

app.use((req, res, next) => {
  console.log('2. Второй middleware');
  next();
});

app.get('/', (req, res) => {
  console.log('3. Обработчик маршрута');
  res.send('OK');
});

// Порядок: 1 → 2 → 3

Важно

Порядок app.use() имеет значение! Middleware выполняются в порядке объявления.


Встроенные Middleware

// Парсинг JSON
app.use(express.json({ limit: '10mb' }));

// Парсинг URL-encoded
app.use(express.urlencoded({ extended: true }));

// Статические файлы
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));

Популярные сторонние Middleware

cors

npm install cors
const cors = require('cors');

// Разрешить все origins
app.use(cors());

// С настройками
app.use(cors({
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST'],
  credentials: true
}));

helmet

npm install helmet
const helmet = require('helmet');

// Защита HTTP заголовков
app.use(helmet());

morgan

npm install morgan
const morgan = require('morgan');

// Логирование запросов
app.use(morgan('dev'));      // Краткий формат
app.use(morgan('combined')); // Полный формат

compression

npm install compression
const compression = require('compression');

// Сжатие ответов
app.use(compression());

Создание Middleware

Логгер

function logger(req, res, next) {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
  });
  
  next();
}

app.use(logger);

Аутентификация

function auth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Token required' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
}

// Использование
app.get('/profile', auth, (req, res) => {
  res.json({ user: req.user });
});

Валидация

function validate(schema) {
  return (req, res, next) => {
    const { error } = schema.validate(req.body);
    
    if (error) {
      return res.status(400).json({ 
        error: error.details[0].message 
      });
    }
    
    next();
  };
}

// Использование с Joi
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email().required()
});

app.post('/users', validate(userSchema), (req, res) => {
  res.json(req.body);
});

Rate Limiting

function rateLimit(windowMs, max) {
  const requests = new Map();
  
  return (req, res, next) => {
    const ip = req.ip;
    const now = Date.now();
    
    if (!requests.has(ip)) {
      requests.set(ip, { count: 1, start: now });
      return next();
    }
    
    const data = requests.get(ip);
    
    if (now - data.start > windowMs) {
      requests.set(ip, { count: 1, start: now });
      return next();
    }
    
    if (data.count >= max) {
      return res.status(429).json({ error: 'Too many requests' });
    }
    
    data.count++;
    next();
  };
}

// 100 запросов в минуту
app.use('/api', rateLimit(60000, 100));

Middleware для конкретных маршрутов

// Для одного маршрута
app.get('/admin', auth, adminOnly, (req, res) => {
  res.json({ admin: true });
});

// Для группы маршрутов
app.use('/api/admin', auth, adminOnly);

// Для роутера
const adminRouter = express.Router();
adminRouter.use(auth);
adminRouter.use(adminOnly);

Async Middleware

// Обёртка для async функций
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Использование
app.get('/users', asyncHandler(async (req, res) => {
  const users = await User.find();
  res.json(users);
}));

Практика

Задание 1: Логгер с временем

Задача: Создай middleware, который логирует время запроса.

Решение:

function requestTime(req, res, next) {
  req.requestTime = new Date().toISOString();
  console.log(`[${req.requestTime}] ${req.method} ${req.url}`);
  next();
}

app.use(requestTime);

Задание 2: API Key

Задача: Создай middleware для проверки API ключа.

Решение:

function apiKey(req, res, next) {
  const key = req.headers['x-api-key'];
  
  if (key !== process.env.API_KEY) {
    return res.status(403).json({ error: 'Invalid API key' });
  }
  
  next();
}

app.use('/api', apiKey);

Проверь себя

  1. Что делает функция next()?
  2. Как применить middleware к конкретному маршруту?
  3. Как обработать ошибки в async middleware?