Урок по треку Node.js и бэкенд 8 мин

HTTP-сервер, Express и NestJS

Модуль http, middleware в Express, модули и DI в NestJS, жизненный цикл запроса.

Почти любой бэкенд на Node это HTTP-сервер. Под капотом всегда модуль node:http, сверху фреймворк: минималистичный Express, быстрый Fastify или структурный NestJS.

Голый node:http

import http from 'node:http';

const server = http.createServer(async (req, res) => {
  if (req.method === 'POST' && req.url === '/echo') {
    const chunks = [];
    for await (const chunk of req) chunks.push(chunk);
    const body = JSON.parse(Buffer.concat(chunks).toString());
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(body));
    return;
  }
  res.writeHead(404).end();
});
server.listen(3000);

Что видно из примера:

  • req это Readable-поток: тело запроса приходит кусками, его надо прочитать.
  • res это Writable-поток: сначала статус и заголовки, потом тело, в конце end().
  • Роутинга, парсинга JSON и обработки ошибок нет: это и добавляют фреймворки.

Если попытаться выставить заголовок после того, как ответ начал отправляться, будет ошибка ERR_HTTP_HEADERS_SENT. Типичная причина: два res.send в одном обработчике, например забытый return после ответа об ошибке.

Express: цепочка middleware

Express это список функций (req, res, next), которые выполняются по порядку регистрации.

import express from 'express';
const app = express();

app.use(express.json());                  // парсит JSON в req.body
app.use((req, res, next) => {             // логирование
  const start = Date.now();
  res.on('finish', () => console.log(req.method, req.url, Date.now() - start));
  next();
});

app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) return res.status(404).json({ error: 'not found' });
  res.json(user);
});

app.use((err, req, res, next) => {        // обработчик ошибок: 4 аргумента
  res.status(500).json({ error: 'internal' });
});

Правила:

  • middleware либо отвечает, либо вызывает next(). Если не сделать ни того, ни другого, запрос повиснет.
  • next(err) пропускает обычные middleware и идёт к обработчику ошибок. Express узнаёт его по четырём параметрам.
  • Без express.json() req.body будет undefined.
  • Express 5 (актуальная версия) сам ловит отклонённые промисы из async-обработчиков и передаёт их в next(err). В Express 4 такая ошибка улетала мимо, и нужна была обёртка.

NestJS: структура и DI

NestJS строит приложение из модулей, контроллеров и провайдеров и использует внедрение зависимостей. По умолчанию работает поверх Express, можно переключить на Fastify.

@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}
  findOne(id: string) { return this.prisma.user.findUnique({ where: { id } }); }
}

@Controller('users')
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get(':id')
  @UseGuards(JwtAuthGuard)
  findOne(@Param('id', ParseUUIDPipe) id: string) {
    return this.users.findOne(id);
  }
}

Контейнер DI сам создаёт UsersService и передаёт его в контроллер. По умолчанию провайдеры синглтоны на всё приложение.

Путь запроса через NestJS:

  1. Middleware (как в Express).
  2. Guards: можно ли выполнять запрос (авторизация, роли).
  3. Interceptors до обработчика (логирование, кеш, таймаут).
  4. Pipes: валидация и преобразование параметров (ValidationPipe, ParseIntPipe).
  5. Обработчик контроллера.
  6. Interceptors после: могут преобразовать ответ.
  7. Exception filters: ловят исключения и формируют ответ об ошибке.

Express, Fastify или NestJS

  • Express: минимум, огромная экосистема, свобода и ответственность за структуру.
  • Fastify: заметно быстрее за счёт схем и быстрой сериализации, встроенная валидация по JSON Schema, плагины с инкапсуляцией.
  • NestJS: архитектура из коробки (модули, DI, декораторы), удобен для больших команд, работает поверх Express или Fastify.

Частые ошибки

  • Забытый return после ответа: ERR_HTTP_HEADERS_SENT.
  • Обработчик ошибок с тремя параметрами: Express не считает его обработчиком ошибок.
  • Middleware, зарегистрированный после роутов, которых он должен касаться.
  • Тяжёлая синхронная работа в обработчике: блокирует все запросы.
  • В NestJS хранить данные запроса в поле синглтон-сервиса: их увидят параллельные запросы.

Что спросят на собесе

  • Что такое middleware и как работает next.
  • Как в Express обработать ошибку из async-обработчика.
  • В каком порядке выполняются guards, interceptors, pipes и filters в NestJS.
  • Что такое DI и зачем он во фреймворке.
  • Чем Fastify отличается от Express.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.