Руководство по миграции в NestJS 12: ESM, стандартные схемы и возможности отслеживания
В этом руководстве описаны основные изменения в NestJS 12 — пакеты ESM, проверка схем по стандарту, встроенная возможность отслеживания и обновления CLI — а также способы безопасной миграции.
Вышла версия NestJS 12, и в отличие от обычных крупных обновлений эта версия не сосредоточена на одной-единственной ключевой функции.
Вместо этого она одновременно затрагивает несколько аспектов экосистемы NestJS, обновляя их в соответствии с современными стандартами разработки бэкенда.
Среди самых значимых обновлений:
- Пакеты Nest теперь распространяются в формате ESM
- Валидация основана на стандарте Standard Schema
- Сериализация также основана на стандарте Standard Schema
- Встроенная возможность отслеживания через
@nestjs/observe - Переписанный интерфейс командной строки NestJS CLI
- Поддержка Rspack для новых структур monorepo
- Vitest и oxlint по умолчанию включены в новые проекты
- Улучшенное обнаружение конфликтующих маршрутов
- Коды ошибок, которые можно парсить инструментами
- Структурированная логгинг-система, удобная для обработки машинами
Если у вас уже есть кодовая база на NestJS, есть один момент, который сразу может развеять все опасения:
Перевод приложения на формат ESM не является обязательным только потому, что сам NestJS 12 поставляется в формате ESM.
Именно этот факт превращает то, что могло бы показаться радикальным обновлением, в процесс, который можно осуществить в собственном темпе.
NestJS 12 сосредоточен на модернизации фреймворка
NestJS широко используется для создания хорошо организованных бэкенд-сервисов на основе Node.js и TypeScript.
Его общая структура не изменилась и остается понятной для всех, кто ранее им пользовался:
NestJS 12 Is About Modernizing the Framework
NestJS has become one of the popular ways to build structured backend applications with Node.js and TypeScript.
Its architecture is familiar:
Изменилась лишь среда Node.js вокруг него.
Использование формата ESM продолжает расти во всей экосистеме.
Библиотеки для работы со схемами, такие как Zod, приобретают популярность.
Более новые и быстрые инструменты сборки заменяют устаревшие средства.
В процессе разработки видимость становится всё более важным фактором, рассматриваемым на одном уровне с другими ключевыми аспектами, а не чем-то, что добавляется уже после выпуска сервиса.
NestJS 12 по сути одновременно соответствует всем этим тенденциям.
Однако примечательно то, что ничто из этого не заставляет существующие приложения принимать всё сразу.
1. Основные пакеты Nest теперь поставляются в формате ESM
Возможно, самым заметным изменением в этой версии является то, что основные пакеты Nest теперь публикуются в формате ESM.
Если ваш проект построен на CommonJS, это может показаться требованием к полной переработке кода.
К счастью, современные версии Node.js поддерживают require(esm).
На практике это означает, что большинство приложений на CommonJS могут продолжать работать без изменений, без полной конвертации в формат ESM.
Например, эта строка продолжает работать точно так же, как и раньше:
const { NestFactory } = require('@nestjs/core');
Вас не обязывают переписать код в следующем виде:
import { NestFactory } from '@nestjs/core';
Тем не менее, NestJS 12 повышает минимальную версию Node.js, требуемую для работы.
Конкретно, вам понадобится одна из следующих версий:
Node.js 20.19+
or
Node.js 22.12+
Node.js 21.x прямо не поддерживается.
Поэтому прежде чем изменять зависимости NestJS, убедитесь, какая версия Node.js у вас установлена:
node --version
Проверка этого на ранних этапах CI/CD-пайплайна также является разумной мерой предосторожности.
2. Переход на ESM — это выбор, а не обязанность
Этот момент заслуживает особого внимания команд, обслуживающих существующие проекты.
Здесь происходят два отдельных процесса миграции.
Сам NestJS переводит свои пакеты на формат ESM.
Однако ваше приложение не обязано сразу следовать этому примеру.
Другими словами, такая конфигурация совершенно допустима:
Existing CommonJS Application
↓
NestJS 12
↓
Continue running CommonJS
вместо того чтобы быть вынужденными использовать:
CommonJS
↓
Rewrite everything
↓
ESM
↓
NestJS 12
Тем не менее, использование специальных инструментов для работы над проектом может всё равно создавать трудности.
Стоит внимательно проверить:
- специальные скрипты Bootstrap
- каналы сборки
- инструменты тестирования
- настройки бандлера
- нестандартные шаблоны импорта
- инструменты, связанные исключительно с CommonJS
Даже если сам NestJS работает нормально, скрипт или инструмент, от которых вы зависите в других частях процесса сборки, могут не функционировать.
3. Поддержка стандартного схематизированного формата меняет подход к валидации
Одним из наиболее значимых нововведений в NestJS 12 является встроенная поддержка стандартного схематизированного формата.
Если вы недавно работали с TypeScript, скорее всего, вы сталкивались с библиотеками вроде Zod, Valibot или ArkType. Эти инструменты обеспечивают валидацию во время выполнения и одновременно гармонично интегрируются с системой проверки типов TypeScript.
Исторически NestJS использовал DTO на основе классов в сочетании с библиотекой class-validator. Такой подход по-прежнему эффективен и не собираются удалять. NestJS 12 просто предлагает альтернативный способ.
Вот как это выглядит на практике:
@Post()
create(
@Body({
schema: createUserSchema,
})
body: CreateUserDto,
) {
return this.usersService.create(body);
}
Затем его настраивают в глобальном масштабе:
app.useGlobalPipes(
new StandardSchemaValidationPipe(),
);
Благодаря этому сама схема берет на себя задачу проверки входящих запросов. Это особенно удобно, если в вашем кодбейсе уже определены схемы с использованием Zod или другой библиотеки, соответствующей спецификации Standard Schema.
4. Zod интегрируется более прямо с NestJS
Предположим, у вас уже есть схема Zod, определенная следующим образом:
const createUserSchema = z.object({
name: z.string().min(1),
email: z.email(),
});
Вместо того чтобы дублировать эту логику в отдельный слой проверки, специфичный для NestJS, можно напрямую включить существующую схему в обработку запросов.
То же самое касается параметров маршрутов:
@Get(':id')
findOne(
@Param('id', {
schema: z.coerce
.number()
.int()
.positive(),
})
id: number,
) {
return this.usersService.findOne(id);
}
Это сокращает количество повторяющейся логики. Вместо того чтобы хранить один набор правил валидации для клиента и другой — для сервера, команды могут использовать единый схематизированный формат там, где это позволяет их настройка. Кроме того, такие схемы могут использоваться для генерации документации в формате OpenAPI.
5. Стандартная схема применяется также к исходным ответам
Валидация не ограничивается только входящими данными — важны и исходящие.
Рассмотрим ситуацию, когда конечная точка случайно возвращает что-то вроде:
{
"id": 1,
"name": "John",
"passwordHash": "..."
}
Технически обработчик действительно возвращает объект. Но этот объект может раскрывать больше информации, чем предусмотрено контрактом API.
Чтобы решить эту проблему, NestJS 12 включает StandardSchemaSerializerInterceptor, который проверяет и преобразует исходные данные перед тем, как они достигнут клиента.
Например:
@UseInterceptors(
StandardSchemaSerializerInterceptor,
)
@SerializeOptions({
schema: userResponseSchema,
})
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
Результатом является покрытие валидации на обоих этапах цикла запроса:
Client
↓
Request
↓
Schema Validation
↓
Application
↓
Schema Serialization
↓
Response
↓
Client
Для сервисов, построенных в основном на API, такая симметрия представляет собой значимое улучшение.
6. Встроенная возможность наблюдаемости с помощью @nestjs/observe
NestJS 12 также вводит специальный пакет для наблюдаемости:
@nestjs/observe
Особенностью этого пакета является его осведомленность о внутренней структуре NestJS. Обычный агент мониторинга сможет увидеть лишь что-то вроде:
POST /users
200
В отличие от этого, собственные инструменты Nest могут распознавать более высокоуровневые конструкции, такие как контроллеры, провайдеры, резолверы GraphQL, потребители очередей, задачи и микросервисы.
Инструментарий охватывает несколько областей, включая HTTP, GraphQL, gRPC, микросервисы, потребители очередей и задачи типа cron.
Цель заключается в том, чтобы рассматривать возможность наблюдения за работой приложения как нечто, встроенное в его жизненный цикл, а не как что-то, добавляемое извне на уровне HTTP-сервера.
7. Возможность наблюдения — это то, чем должен заниматься отдел тестирования
С точки зрения отдела тестирования этот слой возможности наблюдения заслуживает внимания.
Тестирование не должно прекращаться сразу после того, как API отправляет ответ:
200 OK
Есть смысл понимать, что на самом деле происходило во время формирования этого ответа.
Рассмотрим запрос, проходящий через систему следующим образом:
Request
↓
Controller
↓
Service
↓
Database
↓
External API
↓
Response
Если выполнение запроса занимает три секунды, один лишь статус 200 не раскрывает всей картины.
На самом деле важно знать, на что были потрачены эти три секунды.
Возможные причины включают:
- медленные запросы к базе данных
- задержки от внешнего API
- время, затраченное на обработку в логике приложения
Данные для наблюдаемости предоставляют командам QA и инженеров дополнительный набор доказательств, помогая связать сбои тестов с реальным поведением приложения в производстве.
8. Проверка конфигурации переходит на стандартную схему
Работа с конфигурацией — еще одна область, которая проходит обновление.
Ранее многие приложения NestJS использовали для этой цели Joi:
ConfigModule.forRoot({
validationSchema: schema,
});
В NestJS 12 проверка конфигурации теперь переходит на использование стандартной схемы.
Вот как это выглядит на практике:
ConfigModule.forRoot({
validationSchema: z.object({
NODE_ENV: z
.enum([
'development',
'production',
'test',
])
.default('development'),
PORT: z.coerce
.number()
.default(3000),
}),
});
Joi не устраняется — существующие проекты могут продолжать его использовать, но им потребуется обновиться до Joi 18 или новее версии, а также переместить все настройки, связанные с библиотекой, в соответствующие разделы:
validationOptions.libraryOptions
Это часть более широких усилий по созданию общего интерфейса схемы во всей экосистеме фреймворка.
9. Теперь можно автоматически обнаруживать конфликтующие маршруты
Существует тонкая проблема в дизайне API, которую часто трудно заметить до тех пор, пока она не приведёт к проблемам.
Предположим, вы определили эти два обработчика:
@Get(':id')
findOne() {}
@Get('me')
getCurrentUser() {}
В зависимости от способа разрешения маршрутов и порядка их объявления запрос к:
/users/me
может совпасть с шаблоном:
/users/:id
вместо того чтобы попасть в специальный обработчик /me, как планировалось.
NestJS 12 добавляет фичу диагностики с возможностью включения для выявления подобной неоднозначности маршрутов.
Вы включаете её следующим образом:
const app = await NestFactory.create(
AppModule,
{
routeConflictPolicy: {
duplicate: 'error',
shadow: 'warn',
},
routeResolutionStrategy:
'specificity',
},
);
Это позволяет разработчикам заранее обнаруживать неоднозначные правила маршрутизации, вместо того чтобы столкнуться с ними позже из-за запутанного ответа API.
10. Коды ошибок, которые машины действительно могут парсить
Ещё одно небольшое дополнение здесь может оказаться очень важным для всех, кто использует ваш API.
Возьмём эту исключение:
throw new BadRequestException(
'Password is too weak',
);
Разработчик фронтенда может быть склонен сравнивать текст сообщения напрямую:
if (message === 'Password is too weak') {
...
}
Такой подход хрупок, поскольку формулировки могут меняться.
Более надёжным решением будет использование стабильного кода ошибки:
throw new BadRequestException(
'Password is too weak',
{
errorCode: 'WEAK_PASSWORD',
},
);
Тогда клиент сможет проверять его:
WEAK_PASSWORD
вместо того чтобы полагаться на точную формулировку сообщения.
Это становится ещё важнее, когда у API есть несколько потребителей, таких как:
- веб-фронтенд
- мобильное приложение
- API для партнёров
- внутренние сервисы
Все они могут полагаться на один и тот же единый идентификатор ошибки вместо парсинга человекочитаемого текста.
Структурированное логирование получает улучшения
В этой версии также произошли улучшения в функционале логгинга.
Теперь вы можете писать что-то вроде:
logger.log(
'User created',
{
userId: 1,
email: 'foo@bar.com',
},
);
Аргумент объекта рассматривается как структурированные данные, присоединённые к конкретной строке лога, а не просто как дополнительный текст для вывода.
При включённом режиме вывода в формате JSON эти структурированные данные отображаются под ключом params, либо их можно напрямую включить в запись лога с помощью опции flattenParams.
Это очень важно, если ваши логи поступают в систему мониторинга или инструменты отслеживания. Вместо того чтобы выводить простые строки, которые потом необходимо парсить, ваше приложение может генерировать структурированные записи, которые с самого начала можно искать и фильтровать.
Например, запись лога может выглядеть так:
{
"message": "User created",
"params": {
"userId": 1,
"email": "foo@bar.com"
}
}
Такой формат гораздо проще для поиска, чем попытки извлечь поля из сообщения в простом тексте.
CLI был полностью переработан
Ещё одним значительным изменением в этой версии является полная переработка CLI.
Его кодовая база была перенесена на формат ESM. Набор тестов заменился с Jest на Vitest. Для команд CLI была добавлена обработка тестирования «от начала до конца», а внутренняя структура команд была переработана с использованием типизированных объектов контекста.
Всё это не обязательно влияет напрямую на код вашего приложения. Однако это свидетельствует о том, что усилия по модернизации не ограничиваются самим временем выполнения — современизируются также инструменты и рабочий процесс разработчика.
nest upgrade упрощает процесс миграции
В переработанном CLI появилась новая команда:
nest upgrade
Прежде чем что-либо применять, вы можете ознакомиться с тем, что предполагается изменить:
Before running it, you can preview the changes:
Это важно, поскольку повышение версии часто затрагивает множество небольших, не связанных между собой деталей конфигурации. Команда обновления может автоматически обрабатывать такие изменения, как:
- повышение версий пакетов
@nestjs/* - обновление конфигурации webpack
- замена GraphQL Playground на GraphiQL
- настройка способа передачи данных в GraphQL
- обновление пакетов, связанных с NATS
- настройка использования
@nestjs/config - обновление зависимостей Jest
- обновление зависимостей Joi
После завершения работы программа выводит краткое описание того, что было изменено автоматически, и чего всё ещё требуется ручная проверка.
Новые проекты создаются с современными стандартами
Создание совершенно нового проекта с NestJS 12 теперь предоставляет иной стартовый уровень.
При создании новых монорепозиториев по умолчанию используется Rspack в качестве инструмента для сборки. В новых проектах вместо ESLint применяется oxlint. Vitest теперь является стандартным инструментом для выполнения тестов в проектах на основе ESM. Bun также принимается в качестве варианта менеджера пакетов наряду с существующими опциями:
npm
yarn
pnpm
Все это не влияет ретроактивно на существующие проекты — это важный момент. NestJS 12 просто устанавливает более современные стандарты для новых проектов, позволяя уже существующим приложениям мигрировать в собственном темпе.
При настройке GraphQL необходимо проявлять осторожность
Если вы запускаете приложение на GraphQL, существуют этапы миграции, которые нельзя пропускать.
GraphiQL теперь заменяет GraphQL Playground в качестве стандартного инструмента для разработки. Что ещё важнее, поддержка:
subscriptions-transport-ws
полностью устранена. От вас ожидается переход на:
graphql-ws
Эти два протокола несовместимы друг с другом на уровне передачи данных. Это означает, что изменение способа передачи подписок в вашем бэкенде — это не только изменение на стороне бэкенда: все компоненты, которые используют эти подписки, также должны быть обновлены и протестированы.
NestJS API
↓
GraphQL Subscription
↓
Web / Mobile Client
Одного лишь обновления зависимости на стороне сервера будет недостаточно для обеспечения корректной работы всей системы от начала до конца.
Также изменилась поддержка NATS
Теперь фреймворк заменяет пакет nats на:
@nats-io/transport-node
Если ваше приложение напрямую импортирует старый пакет, вам потребуется обновить как саму зависимость, так и соответствующие инструкции импорта.
Также произошли изменения в обработке пакетов: теперь полезные данные сериализуются в виде строк JSON, и любой пользовательский десериализатор будет получать полный объект сообщения NATS, а не заранее обработанные данные. Содержимое полезных данных можно прочитать с помощью:
msg.json()
Если обмен сообщениями является ключевой частью вашей системы, это аспект, который стоит отдельно упомянуть в планах интеграционных и регрессионных тестов.
17. Изменился порядок выполнения хуков жизненного цикла
Ещё одно изменение, влияющее на работу системы, связано с хуками жизненного цикла.
В NestJS 12 порядок выполнения хуков жизненного цикла теперь зависит от местоположения компонента в иерархии.
Это имеет значение, если ваше приложение зависит от определённой последовательности действий во время:
- инициализации
- запуска
- остановки работы
- демонтажа
Предположим, что сервис запускает ресурсы в следующем порядке:
Database
Queue
Cache
External API
Другой сервис предполагает, что один из этих ресурсов уже доступен. После обновления вам следует проверить, сохраняется ли это предположение.
Именно такие изменения не обязательно приводят к сбою компиляции. Ваш проект может скомпилироваться без ошибок, но при выполнении работать иначе.
18. Дополнительные изменения, о которых стоит знать
В этой версии также внесено несколько других корректировок.
NestJS 12 также затрагивает:
- формат ответов на ошибки валидации
- способ обработки исключений gRPC
- сопоставление шаблонов регулярных выражений в Kafka
- веб-шлюзы WebSocket с диапазоном запросов
- причины, сообщаемые при разрыве соединения WebSocket
- функции-хуки перед запросом для микросервисов
- поведение плавного завершения работы в Express
- способ, которым адаптер HTTP маппит ошибки
Большинство проектов не будут использовать все эти функции. Но там, где ваше приложение действительно зависит от одной из них, стоит добавить целенаправленные тесты на регрессию для нее.
19. На чем должен сосредоточиться QA после обновления?
Это, пожалуй, самый важный вопрос, на который нужно ответить.
Подтверждение того, что обновление крупной фреймворковой библиотеки прошло без проблем, не должно основываться лишь на запуске:
npm test
Вместо этого тестирование следует разделить на отдельные области.
API
Проверьте:
- аутентификацию
- авторизацию
- валидацию
- ответы на ошибки
- соответствие маршрутам
- сериализацию ответов
Конфигурация
Проверьте:
- обязательные переменные окружения
- недопустимые значения
- значения по умолчанию
- конфигурацию для производства
- конфигурацию для тестирования
GraphQL
При необходимости:
- запросы
- мутации
- подписки
- GraphiQL
- совместимость с клиентами
Микросервисы
При наличии соответствующих данных:
- NATS
- Kafka
- gRPC
- сериализация сообщений
- повторные попытки
- обработка исключений
Наблюдаемость
Если функция включена:
- трейсы HTTP
- трейсы GraphQL
- фоновые задачи
- потребители очередей
- ошибки
- задачи cron
Остановка работы
Необходимо проверить:
- обработку сигнала SIGTERM
- активные запросы
- соединения с базой данных
- очереди
- фоновые работники
Цель не в том, чтобы ответить на вопрос:
"Запускается ли приложение?"
А в том, чтобы ответить на вопрос:
"Правильно ли приложение продолжает работать во всех важных сценариях?"
20. Что означает эта версия для работы по тестированию качества
Существует более общая тенденция, на которую стоит обратить внимание.
Фреймворки становятся всё более автоматизированными. Процессы верификации приобретают стандартизированный характер. Механизмы наблюдаемости встраиваются непосредственно в код. Журналы логов по умолчанию становятся структурированными. Конфликты маршрутов можно обнаруживать автоматически. Инструменты тестирования становятся всё быстрее.
Ничто из этого не устраняет необходимость в тестировании качества. Это лишь меняет сферы, где тестирование приносит наибольшую пользу.
Вместо того чтобы задавать только вопрос:
"Работает ли этот эндпоинт?"
специалисты по тестированию качества должны всё чаще задавать:
«Является ли контракт API всё ещё корректным?» «Могут ли быть зафиксированы сбои?» «Правильно ли применяются разрешения?» «Являются ли ошибки читаемыми для машины?» «Корректна ли сериализация?» «Восстанавливается ли приложение так, как следует?» «Изменяет ли это обновление существующее поведение?»
Фреймворк может автоматизировать некоторые проверки самостоятельно. Но человек всё равно должен решить, что именно стоит проверять в первую очередь.
21. Рекомендуемый путь миграции в NestJS 12
Вместо прямого обновления продакшн-среды сначала проверьте свою среду:
node --version
Убедитесь, что вы используете:
Node 20.19+
или:
Node 22.12+
Затем обновите CLI:
npm i -g @nestjs/cli@latest
Посмотрите, что произойдёт в результате миграции:
nest upgrade --dry-run
Тщательно изучите результаты. Затем примените их:
nest upgrade
После этого запустите:
npm test
вместе со своими интеграционными и полноценными тестовыми наборами.
Особое внимание уделите любым функциям, построенным на:
- GraphQL
- NATS
- проверке конфигурации
- собственных трубках обработки данных
- хуках жизненного цикла
- Webpack
- инструментах на основе CommonJS
Каждая из этих областей требует отдельного рассмотрения с точки зрения аспектов миграции.
Заключение
NestJS 12 — это не просто добавление ещё одной функции.
Это шаг к согласованию NestJS с текущим направлением развития экосистем Node.js и TypeScript.
ESM теперь встроен непосредственно в архитектуру пакетов.
Стандартная схема открывает возможность использования библиотек проверки данных, таких как Zod, Valibot, ArkType и другие.
Та же экосистема схем может использоваться также для сериализации.
Возможности отслеживания теперь более прямо связаны с собственной структурой приложения Nest.
Интерфейс командной строки был перестроен с учётом более современных инструментов.
Rspack, Vitest, oxlint и Bun становятся частью современной конфигурации NestJS.
В то же время ничто из этого не заставляет существующие приложения менять всё сразу.
Вы можете оставаться на CommonJS.
Вы можете продолжать использовать валидацию на основе классов.
Переход на Vitest или oxlint не является обязательным сразу.
Эта гибкость, пожалуй, является самым практичным аспектом этого выпуска.
NestJS 12 обновляет фреймворк, не требуя от каждого существующего приложения мгновенной модернизации.
Для разработчиков это означает больше возможностей для выбора собственного темпа работы.
Для инженеров по тестированию это означает ещё одно крупное обновление фреймворка, которое необходимо проверить — не только на уровне кода, но и в отношении API, интеграций, возможностей мониторинга, конфигурации и реального поведения в производственных условиях.
Именно здесь обновления фреймворков становятся интересными.
Изменение номера версии в файле package.json — это простая часть.
На самом деле важно то, будет ли приложение продолжать работать так, как ожидают те, кто от него зависит.
Связанные статьи
- Общее использование одной схемы Zod между фронтендом на React и бэкендом на Node — Узнайте, как одна схема Zod может проверять формы в React, ответы API, тела запросов Express и переменные окружения, одновременно генерируя соответствующие типы TypeScript.