Посібник з міграції на 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
require(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 ви використовуєте:
node --version
Перевірка цього на ранніх етапах у вашому CI/CD-пайплайні також є розумною запобіжною мірою.
2. Перехід на ESM — це вибір, а не обов’язок
Цей момент потребує особливої уваги з боку команд, які підтримують існуючі проекти.
Тут відбуваються два окремі процеси міграції.
Sам 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. Можливість спостереження — це те, про що має піклуватися QA
З точки зору QA цей шар можливості спостереження заслуговує уваги.
Тестування не повинно припинятися в момент, коли API надсилає відповідь:
200 OK
Є значення у розумінні того, що насправді відбувалося під час генерації цієї відповіді.
Розгляньмо запит, який проходить через систему ось так:
Request
↓
Controller
↓
Service
↓
Database
↓
External API
↓
Response
Якщо виконання виклику займає три секунди, сам статус 200 не розповідає всієї історії.
Те, що ви насправді хочете знати, — це де були витрачені ці три секунди.
Можливими причинами є:
- повільні запити до бази даних
- затримки від зовнішнього API
- час, витрачений на обробку логіки додатку
Дані для спостереження надають командам QA та інженерів додатковий рівень доказів для роботи, допомагаючи поєднати невдачі тестів із реальною поведінкою продукту.
8. Перевірка конфігурації переходить на стандартну схему
Обробка конфігурації — ще одна сфера, яка проходить оновлення.
Раніше багато додатків NestJS використовували Joi для цього:
ConfigModule.forRoot({
validationSchema: schema,
});
У NestJS 12 перевірка конфігурації починає використовувати стандартну схему замість Joi.
Ось як це виглядає на практиці:
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 тепер забезпечує інший вихідний пункт для роботи.
Нові налаштування monorepo за замовчуванням використовують Rspack як інструмент для об’єднання пакетів. У нових проектах замість ESLint використовується oxlint. Vitest тепер є стандартним інструментом для виконання тестів у проектах на основі ESM. Bun також приймається як один із варіантів менеджера пакетів, поруч із існуючими варіантами:
npm
yarn
pnpm
Це ніяк не впливає ретроактивно на існуючі проекти — це важлива різниця. NestJS 12 просто встановлює більш сучасні стандарти для всього, що створюється в майбутньому, дозволяючи існуючим застосункам мігрувати у власному темпі.
Налаштування GraphQL вимагають уваги
Якщо ви керуєте застосунком на GraphQL, є завдання з міграції, які не варто ігнорувати.
GraphiQL тепер замінює GraphQL Playground як стандартний інструмент у IDE. Ще важливіше те, що підтримка:
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 тепер є частиною самої архітектури пакетів.
Стандарт Schema відкриває можливості фреймворку для використання бібліотек перевірки даних, таких як Zod, Valibot, ArkType та інших.
Ця сама екосистема схем також може використовуватися для серіалізації.
Здатність до спостереження тепер більш прямо пов’язана з власною структурою додатку Nest.
CLI було перебудовано навколо новіших інструментів.
Rspack, Vitest, oxlint та Bun стають частиною сучасної конфігурації NestJS.
Водночас жоден з цих елементів не змушує існуючі додатки миттєво все змінювати.
Ви можете залишитися з CommonJS.
Ви можете й надалі використовувати перевірку на основі класів.
Перехід на Vitest чи oxlint не є обов’язковим відразу.
Ця гнучкість, мабуть, є найпрактичнішим аспектом цього випуску.
NestJS 12 оновлює фреймворк, не вимагаючи від кожного існуючого додатку миттєво все модернізувати.
Для розробників це означає більше можливостей обирати власний темп роботи.
Для інженерів з контролю якості це означає ще одне значне оновлення фреймворку, яке потрібно перевірити — не лише на рівні коду, а й щодо API, інтеграцій, можливостей моніторингу, конфігурації та реальної поведінки у продакшені.
Саме тут оновлення фреймворку стають цікавими.
Зміна номера версії в package.json — це проста частина.
Насправді важливо те, чи продовжує додаток працювати так, як очікують ті, хто від нього залежить.
Пов’язані статті
- Розподіл однієї схеми Zod між вашим React-фронтендом та Node-бекендом — Дізнайтеся, як одна схема Zod може перевіряти форми React, відповіді API, тіла запитів Express та змінні середовища, водночас генеруючи відповідні типи TypeScript.