
Надійні REST API на NestJS: правила проєктування
- NestJS REST API
- API design
- DTO validation
- pagination
- idempotency
- error contract
- API versioning
- OpenAPI

Надійні REST API на NestJS: правила проєктування
Надійний API поводиться передбачувано, коли клієнти повторюють запити, надсилають неправильні дані, діють одночасно, оновлюються в різний час або стикаються з частковою відмовою. Назви ресурсів — лише видима частина. Глибший контракт охоплює семантику методів, авторизацію, перевірку даних, порядок, подання часу й грошей, коди помилок, поведінку за повторного надсилання, сумісність та операційні докази.
NestJS дає командам узгоджений спосіб виразити цей контракт через контролери, конвеєри перетворення, захисники маршрутів, перехоплювачі, фільтри винятків, модулі й інтеграцію з OpenAPI. Ці механізми не визначають, де починається транзакція, яка служба відповідає за факт, чи можна повторити команду та яка зміна порушить роботу споживача. Надійність виникає тоді, коли такі рішення сформульовано явно й послідовно застосовано.
Починайте зі шляхів споживачів і незмінних правил предметної області. Для кожної кінцевої точки назвіть учасника, ресурс або команду, дозволений стан, перевірку повноважень, обмеження вхідних даних, атомарну зміну, відповідь, помітні побічні наслідки, правило повтору й режими відмови. Залишайте питання передавання даних на межі, а ділові рішення — у прикладних службах або службах предметної області. Наші послуги з розробки на NestJS використовують це розділення, щоб HTTP міг розвиватися, не перетворюючи кожний випуск клієнта на узгоджену аварійну операцію.
Свідомо моделюйте ресурси й семантику HTTP
Використовуйте для ресурсів сталі ділові іменники: /orders/{orderId}, /customers/{customerId}/addresses або /invoices/{invoiceId}/payments. Вкладайте підресурс лише тоді, коли зв’язок із батьківським ресурсом змінює його ідентичність, авторизацію чи життєвий цикл. Глибокі шляхи, що повторюють з’єднання таблиць бази даних, розкривають спосіб зберігання й стають крихкими після зміни відповідальності.
Не кожна ділова дія є оновленням поля. Моделюйте скасування, погодження, імпорт або створення звіту як ресурс, якщо воно має власний стан, дозволи, результат чи історію аудиту: POST /orders/{orderId}/cancellations говорить більше, ніж загальний /execute. Зберігайте контролери тонкими. Вони розбирають транспортні дані, викликають правила та сценарії використання, а потім перетворюють результати; вони не повинні координувати транзакції бази даних або дублювати правила предметної області в різних маршрутах.
Застосовуйте методи HTTP відповідно до їхньої семантики. GET і HEAD мають бути безпечними. PUT замінює стан відомого цільового подання й за визначенням є ідемпотентним; PATCH застосовує задокументовану часткову зміну; DELETE потребує чітких правил повтору та позначення видаленого ресурсу; POST виконує залежне від ресурсу опрацювання й не є ідемпотентним за своєю природою. Не використовуйте GET для дії лише тому, що його зручно викликати з браузера.
Обирайте коди стану як частину контракту. Синхронне створення зазвичай повертає 201 із Location; прийнята фонова робота — 202 і ресурс стану; 204 не містить відповіді; 400 описує неправильно сформовані або недопустимі за правилами дані відповідно до вашої угоди; 401 і 403 відокремлюють відсутню особу від браку повноважень; 404 може навмисно приховувати захищений ресурс; 409 позначає конфлікт стану; 412 відповідає невиконаній умові запиту; а 429 або 503 мають пояснювати, чи доцільний повтор. Одна послідовна політика важливіша за суперечки щодо сусідніх допустимих кодів для кожної кінцевої точки.
Перевіряйте кожну зовнішню межу
Самі типи TypeScript не захищають API під час виконання. Перевіряйте кожне недовірене тіло, параметр шляху, значення запиту, заголовок, файл, сповіщення про подію й твердження з розкодованого токена до того, як воно вплине на запит до даних або ділове рішення. NestJS підтримує класи DTO, позначені декораторами через ValidationPipe і схеми часу виконання за допомогою засобів, сумісних зі Standard Schema. Оберіть один зрозумілий підхід для кожної межі й залишайте його правила часу виконання видимими.
У разі використання класів DTO пам’ятайте, що інтерфейси, узагальнені типи й імпорти лише для типів зникають під час виконання. Глобальний ValidationPipe може перетворювати значення, вилучати неоголошені властивості або відхиляти їх за допомогою forbidNonWhitelisted. Перевірка за схемою може вивести типи TypeScript зі схеми часу виконання та визначити сувору, очищувальну або дозвільну поведінку об’єктів. У кожній моделі перетворення типів має бути свідомим: значення рядка запиту надходять як рядки, а порожній рядок, неправильна дата чи "false" ніколи не повинні несподівано ставати істинним прапорцем.
Розділяйте моделі створення, повної заміни, часткової зміни, запиту й відповіді. Відсутнє значення та null повинні мати різні задокументовані значення. Подавайте гроші цілою кількістю найменших одиниць або за чіткими правилами десяткових чисел і завжди зазначайте валюту; для часу встановіть угоду щодо часового поясу; перевіряйте переліки значень та ідентифікатори без здогадок. Обмежуйте довжину рядків, масиви, глибину об’єктів, розмір файлів і сторінок, діапазони дат та пакетні команди, щоб структурно правильні дані не могли вимагати необмежених ресурсів процесора, пам’яті чи бази даних.
Перевірка даних відповідає на запитання, чи мають вони очікувану форму. Авторизація визначає, чи може ця особа діяти з конкретним об’єктом; незмінні правила предметної області — чи дозволений перехід саме зараз. Ніколи не прив’язуйте дані запиту безпосередньо до сутності ORM і не переносьте всі їхні поля без розбору до операції оновлення. Перенесіть дозволені поля до команди, завантажте достовірний стан, застосуйте правила доступу до об’єкта й незмінні правила, а потім поверніть явний DTO відповіді або модель читання.
app.useGlobalPipes(new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
validationError: { target: false, value: false },
}));
export class ListOrdersQuery {
@IsOptional()
@IsString()
@MaxLength(512)
cursor?: string;
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit = 25;
@IsOptional()
@IsEnum(OrderStatus)
status?: OrderStatus;
}Зробіть поділ на сторінки та фільтрування однозначними
Кожна колекція потребує обмеженого типового й максимального розміру сторінки та повного порядку. Поділ за зміщенням простий, підтримує номери сторінок і може бути доречним для помірних, відносно стабільних адміністративних наборів даних. Великі зміщення можуть спричинити ресурсомістке сканування, а одночасне додавання чи видалення — перемістити рядки між сторінками й створити повтори або пропуски.
Курсорний поділ підходить для довгих або мінливих потоків, якщо курсор містить усі ключі сортування та унікальний додатковий ключ, наприклад (createdAt, id). Наступний запит має відтворювати ті самі фільтри й лексикографічне порівняння. Вважайте курсор непрозорим, керуйте версіями його вмісту, захищайте цілісність і визначайте напрямок. Курсор, що кодує лише неунікальну часову позначку, зрештою пропускатиме або повторюватиме записи.
Дозволяйте лише визначені параметри фільтрування, пошуку, приєднання, вибору полів і сортування. Документуйте оператори, поведінку регістру й наголосів, межі часових поясів, порядок значень null і те, чи повторні фільтри означають AND або OR. Перетворюйте параметри через явну модель запиту замість приймання назв стовпців чи довільних фрагментів SQL. Додавайте індекси для виміряних способів доступу й обмежуйте дорогі діапазони дат, розгортання зв’язків, пошук із довільними підстановними знаками та розмір вивантажених результатів.
Відповідь із колекцією має містити елементи й надійні навігаційні відомості. Точна загальна кількість корисна для переходу між сторінками, але може бути дорогою, залежати від дозволів або змінюватися; надавайте її лише тоді, коли вона потрібна продукту й запит здатен її обчислити. Перевіряйте порожні сторінки, видалені цілі курсора, однакові значення сортування, одночасні записи, неправильні курсори, зміни прав доступу й відновлення роботи клієнта після тривалої паузи.
Проєктуйте ідемпотентність та одночасні зміни разом
Мережа може відмовити після того, як сервер зафіксував зміну, але до отримання відповіді клієнтом. Клієнт не знає, чи існує платіж, замовлення, бронювання або завдання, тому обґрунтований повтор здатен створити дублікат. Для чутливих до повтору команд POST приймайте ключ ідемпотентності, обмежений автентифікованою особою, операцією й часто клієнтською організацією.
Зберігайте ключ, канонічний відбиток запиту, стан опрацювання та придатну до повторного відтворення остаточну відповідь у надійному сховищі. Застосуйте обмеження унікальності для області й ключа. Перший запит закріплює ключ; повтор із тим самим відбитком отримує збережений результат; використання з іншими даними повертає конфлікт. Визначте, що бачить одночасний виклик під час опрацювання, які відмови допускають повтор, скільки живуть записи й чи можна безпечно зберігати очищені дані відповіді.
Ділова зміна та результат ідемпотентності не повинні розходитися. Використовуйте одну транзакцію, якщо вони мають спільну базу даних, або процес із явним проміжним станом і звірянням, якщо залучені зовнішні наслідки. Словник у пам’яті не переживе перезапуску й горизонтального масштабування. Кеш без надійного основного запису може завершити строк дії, поки початковий наслідок і далі існує. Постачальники на наступних етапах також можуть потребувати власного сталого ключа, але їхня гарантія не замінює вашу.
Ідемпотентність запобігає повторному виконанню тієї самої команди, але не розв’язує суперництво двох різних допустимих команд за зміну одного ресурсу. Запобігайте втраченим оновленням за допомогою поля версії, сильного ETag разом із If-Match, команди з очікуваним станом або умови бази даних під час оновлення. Повертайте 412 у разі невиконання умови HTTP або задокументований конфлікт 409, після чого клієнт має повторно завантажити стан і ухвалити рішення. Обмеження унікальності предметної області й далі захищають такі факти, як одна активна підписка або одне зовнішнє посилання.
@Post()
@HttpCode(HttpStatus.CREATED)
async create(
@CurrentUser() principal: UserPrincipal,
@Headers('idempotency-key', ParseIdempotencyKeyPipe) key: string,
@Body() dto: CreateOrderDto,
) {
return this.idempotency.execute({
scope: ['create-order', principal.tenantId, principal.userId],
key,
request: dto,
handler: () => this.createOrder.execute(principal, dto),
});
}
// execute() persists a canonical request fingerprint and result.
// Durable storage enforces one unique record for (scope, key).Зберігайте помилки корисними, сталими й безпечними
Визначте єдину структуру помилки зі сталим машинозчитуваним кодом, безпечним повідомленням для людини, ідентифікатором запиту або трасування та необов’язковими відомостями про помилки в окремих полях. Клієнти розгалужують поведінку за станом і кодом, а не за перекладеним текстом. Зберігайте коди на рівні рішень, які може ухвалити споживач: виправити дані, пройти автентифікацію, запросити доступ, повторно завантажити стан, зачекати, повторити дію чи звернутися до підтримки.
Перетворюйте відомі відмови предметної області й залежностей через централізовані перетворювачі або фільтри винятків, але зберігайте корисний контекст у захищеній телеметрії. Розрізняйте неправильно сформовані дані, невдалу автентифікацію, відхилену авторизацію, приховані або відсутні ресурси, конфлікти поточного стану, невиконані умови, обмеження частоти, завершення строку очікування залежності й тимчасову недоступність. Документуйте можливість повтору та повертайте Retry-After лише тоді, коли він виражає змістовну оцінку сервера або правило.
Не розкривайте стеки викликів, SQL, назви таблиць, внутрішні вузли, облікові дані, токени, необроблені дані постачальника чи чутливі значення перевірки. Відмова авторизації не повинна повідомляти, чи належить ідентифікатор іншій клієнтській організації. Записуйте безпечний код помилки, операцію, тривалість, випуск, категорію залежності й дані зв’язку, потрібні для розслідування; чутливі поля вилучайте, а кількість значень позначок показників обмежуйте.
Контракт помилок стає дієвим лише тоді, коли хтось бачить закономірності. Вимірюйте частоту й затримку за кінцевою точкою, класом коду стану, сталим кодом, залежністю, версією клієнта й випуском. Сповіщайте про вплив на клієнтів і тривале відхилення, а не про кожну окрему помилку 4xx. Простежуйте запит між службами та асинхронною роботою, зберігаючи той самий безпечний ідентифікатор зв’язку.
Розвивайте контракт і перевіряйте OpenAPI
Віддавайте перевагу сумісному розвитку: додавайте необов’язкові поля відповіді, запроваджуйте нові кінцеві точки, послаблюйте вимоги до вхідних даних лише тоді, коли це залишається безпечним, і розширюйте переліки значень лише за умови, що споживачі опрацьовують невідомі варіанти. Зміна змісту може порушити роботу клієнта, навіть якщо форма JSON лишилася такою самою. Перейменування, видалення, посилення вимоги, зміна допустимості null, типових значень, порядку результатів, видимості за правилами авторизації або призначення коду помилки потребують перевірки сумісності.
Запроваджуйте версію, коли обов’язковий контракт має бути порушений і потрібне співіснування. NestJS підтримує версію в URI, заголовку, типі медіа та власний спосіб її визначення. Оберіть одну видиму політику, визначте типову версію й поведінку для непідтримуваної, версіонуйте найменшу цілісну частину контракту. Опублікуйте строк припинення підтримки, поради щодо заміни, відповідального за підтримку й умову видалення. Спостерігайте за використанням автентифікованими клієнтами замість припущення, що старий маршрут нікому не потрібен.
Де можливо, створюйте OpenAPI з тих самих контролерів і контракту часу виконання, визнаючи обмеження відображення типів. Опишіть автентифікацію, очікування щодо авторизації, формати, обмеження, приклади, коди помилок, поділ на сторінки, умовні заголовки й ідемпотентність. Зберігайте DTO відповіді явними, щоб стовпці та зв’язки сховища непомітно не потрапили до специфікації. Розглядайте створений документ як складову продукту, а не як прикрасу Swagger UI.
Зберігайте специфікацію, перевіряйте її статично й порівнюйте в CI. Зупиняйте процес через непогоджені несумісні зміни, за потреби створюйте типи споживачів або випробувальні замінники та запускайте перевірки контрактів постачальника й споживача проти розгорнутої збірки. Додайте перевірки кінцевих точок для правил часу виконання, авторизації об’єктів, перетворення відповіді, станів і заголовків, повторних запитів, одночасних змін та типових відмов. Контракт заслуговує на довіру, коли реалізація, опублікована специфікація, приклади й спостережувана поведінка робочої системи узгоджуються.
Перелік перевірок REST API
- Семантика й відповідальність: ресурси, методи, коди стану, авторизація, переходи станів, межі транзакцій і побічні наслідки відповідають задокументованій поведінці предметної області.
- Обмежені вхідні дані: тіла, параметри, запити, заголовки, файли та сповіщення про події перевіряються під час виконання; невідомі поля й неоднозначне перетворення типів підпорядковуються явним правилам.
- Стабільні колекції: порядок є повним, поділ на сторінки — обмеженим, курсори — непрозорими й правильними, фільтри — у переліку дозволених, а індекси відповідають виміряним способам доступу.
- Безпечний повтор: чутливі до повтору команди використовують надійну ідемпотентність; одночасні записи — попередні умови, версії або очікуваний стан; унікальність предметної області й далі забезпечується.
- Перевірений розвиток: помилки й OpenAPI сталі та машинозчитувані; CI перевіряє сумісність; перевірки контрактів, телеметрія, припинення підтримки й дані про використання клієнтами допомагають керувати змінами.
Чи мають контролери NestJS повертати сутності ORM?
Чи завжди курсорний поділ на сторінки кращий за поділ зі зміщенням?
Яким запитам потрібні ключі ідемпотентності?
Чи гарантує автоматично створена специфікація OpenAPI сумісність API?
Наші дослідження
Дослідження та розробка рішень на основі штучного інтелекту для оптимізації бізнес-процесів і підвищення ефективності прийняття рішень.
Аналіз моделей машинного навчання для прогнозної аналітики у фінансовій сфері, електронній комерції та SaaS-платформах.
Дослідження технологій обробки природної мови та комп'ютерного зору для посилення автоматизації, персоналізації та підтримки клієнтів.


