<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Gyber</title><generator>teletype.in</generator><description><![CDATA[| Calamitas virtutis occasio |]]></description><image><url>https://img4.teletype.in/files/38/61/38613fe6-0ca0-4a10-8134-119a22d84bd9.png</url><title>Gyber</title><link>https://teletype.in/@experiment</link></image><link>https://teletype.in/@experiment?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=experiment</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/experiment?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/experiment?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sun, 05 Apr 2026 15:16:00 GMT</pubDate><lastBuildDate>Sun, 05 Apr 2026 15:16:00 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@experiment/nest-js-progressive-arch</guid><link>https://teletype.in/@experiment/nest-js-progressive-arch?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=experiment</link><comments>https://teletype.in/@experiment/nest-js-progressive-arch?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=experiment#comments</comments><dc:creator>experiment</dc:creator><title>NestJS Backend: опыт, архитектура и лучшие практики для зрелых проектов</title><pubDate>Thu, 01 May 2025 02:36:45 GMT</pubDate><description><![CDATA[Backend — это не просто набор функций, это инфраструктура доверия, ответственности и предсказуемости. Это фундамент, на котором строится бизнес, команда, будущее продукта.]]></description><content:encoded><![CDATA[
  <h2 id="em5t">Вступление: зрелость как инженерный выбор</h2>
  <p id="C5Th"></p>
  <h3 id="aQG8"><em>В мире разработки слишком часто побеждает спешка. Мы привыкли “делать MVP”, “выкатить как-нибудь”, “потом разберёмся”. Но любой, кто хоть раз сталкивался с поддержкой растущего продукта, знает: хаос не прощает легкомыслия. </em></h3>
  <p id="WNOG"></p>
  <blockquote id="LKEF"><em><u>Архитектура — это не набор функций, это инфраструктура доверия, ответственности и предсказуемости - фундамент, на котором строится бизнес, команда, будущее продукта.</u></em></blockquote>
  <p id="7MgL"></p>
  <p id="kR4y">В какой-то момент мы с командой осознали: если мы хотим не просто “выживать”, а развиваться, нам нужно не латать старое, а строить новое — осознанно, системно, с прицелом на годы вперёд.  <br />Мы выбрали путь зрелой архитектуры, где каждый модуль, каждый слой, каждый паттерн — не случайность, а результат анализа, обсуждений и опыта.</p>
  <p id="LFHr">NestJS стал для нас платформой для внедрения инженерной культуры: модульность, строгая типизация, прозрачность зависимостей, поддержка лучших практик из мира TypeScript и Node.js.</p>
  <p id="8cJW"></p>
  <h3 id="ZDVV">Для кого эта статья</h3>
  <h3 id="Wuyp"><em><code>- от хаоса к гармонии</code></em></h3>
  <p id="2JWX"></p>
  <p id="BFMj">Когда-то мы начинали с быстрых MVP: “главное, чтобы заработало”. Но время шло, проекты росли, а с ними росли и требования — к скорости, безопасности, масштабируемости. И вот мы оказались на распутье: либо продолжать латать дыры, либо построить что-то по-настоящему крутое. Мы выбрали второе.</p>
  <p id="ftHd">NestJS стал нашим верным спутником в этом приключении. Этот фреймворк — как швейцарский нож для разработчика: модульный, строгий, с поддержкой TypeScript и кучей готовых инструментов. Но даже с таким помощником без правильного подхода можно заблудиться. Поэтому мы взялись за дело с умом: продумали архитектуру, внедрили лучшие практики и отточили процессы. И знаете что? Получилось нечто, чем хочется поделиться с вами.</p>
  <p id="oPYE">Эта статья содержит опыт, выстраданный через десятки итераций, бессонные ночи и горячие споры в команде. Здесь вы найдёте всё: от структуры проекта до мелочей вроде логирования и тестов. А ещё — немного вдохновения, чтобы ваш следующий проект стал шедевром.</p>
  <p id="tOzA">Эта статья пишется для тех, кто устал от хаоса и хочет порядка. Для тех, кто строит не “ещё один сервис”, а платформу, которую не стыдно показать коллегам, инвесторам, самому себе через год.  </p>
  <p id="Vm6c"><br />Здесь не будет банальных советов и поверхностных решений. Только проверенные подходы, реальные грабли, архитектурные решения, которые выдержали нагрузку и время. Это систематизация опыта: от структуры папок до CI/CD, от DTO-first API до Docker-окружения, от логирования до тестов.  <br /></p>
  <p id="vTVm"><strong><em>Здесь вы найдёте не только “как”, но и “зачем” — мотивацию, причины, последствия каждого решения.</em></strong></p>
  <p id="vYpi"></p>
  <p id="gTXS">Если вы ищете не волшебную таблетку, а рабочую систему — добро пожаловать.  <br />Пусть ваш backend станет  надёжным и удобным для команды, прозрачным для бизнеса, готовым к росту и переменам.</p>
  <p id="BtK0"></p>
  <h3 id="f6bb">Введение:</h3>
  <p id="a7It">В современном мире backend-разработки требования к качеству, безопасности и поддерживаемости кода постоянно растут. Мы прошли путь от “быстрого MVP” до архитектуры, которая выдерживает нагрузку, легко расширяется и радует как разработчиков, так и бизнес.  В этой статье я расскажу, как мы шаг за шагом строили современный backend на NestJS, довели его до уровня, соответствующего требованиям крупных production-проектов, и какие подходы, паттерны и инструменты мы внедрили для удобства, безопасности и масштабируемости.</p>
  <p id="IvvZ"></p>
  <p id="h1cA"><strong>Статья будет полезна:<br /></strong>- начинающим и опытным backend-разработчикам,<br />- тимлидам и архитекторам,<br />- всем, кто хочет понять, как строить поддерживаемые и надёжные серверные приложения.</p>
  <p id="dqkA"></p>
  <h2 id="LKsT">1. Архитектура проекта: фундамент для роста</h2>
  <p id="Gdx5"></p>
  <h3 id="DsKh">1.1. Модули и слои: разделяй и властвуй</h3>
  <p id="f3KD"></p>
  <p id="p0Xf"><em>Всё начинается с архитектуры. Мы строим проект по принципу “один бизнес-домен — один модуль”.  <br />Это значит, что у нас есть отдельные модули для пользователей, проектов, аутентификации и т.д.</em></p>
  <p id="Tmgo"></p>
  <p id="RobK"><strong>В каждом модуле:</strong></p>
  <p id="SbLX">- <strong>controllers/ </strong>— только маршрутизация и валидация входных данных. Здесь нет бизнес-логики!</p>
  <p id="gisc"><br />- <strong>services/ </strong>— бизнес-логика, работа с БД, валидация бизнес-правил. Только здесь решается, “можно ли” и “как именно”.</p>
  <p id="be6t"><br />- <strong>dto/</strong> — только DTO, специфичные для этого модуля. DTO — это “контракт” между backend и frontend.</p>
  <p id="qh3N"><br />- <strong>entities/ </strong>— только TypeORM-сущности. Это “отражение” таблиц в базе.</p>
  <p id="NeQM"></p>
  <p id="OKLs"><strong>Пример структуры:<br /></strong></p>
  <p id="KrYx"><code>src/<br />  modules/<br />    users/<br />      controllers/<br />      services/<br />      dto/<br />      entities/<br />    projects/<br />    auth/<br />  shared/<br />    dto/<br />  common/<br />    filters/<br />    utils/<br />    dto/</code></p>
  <p id="UVM2"><br /><br /><strong>Почему это важно?</strong></p>
  <p id="Wzth"><strong><br /><em><u>Такой подход позволяет легко масштабировать проект, добавлять новые фичи, не боясь “сломать всё”. Каждый модуль — как мини-приложение, но с едиными стандартами.</u></em></strong></p>
  <p id="rKoE"></p>
  <h3 id="hGWL">1.2. Shared и Common: не дублируй, а делись</h3>
  <p id="COur"></p>
  <p id="bo1N">В зрелом проекте архитектура должна не только обеспечивать масштабируемость, но и минимизировать дублирование кода, облегчать сопровождение и ускорять разработку новых фич. Для этого мы чётко разделяем два слоя: <strong>shared</strong> и <strong>common</strong>.</p>
  <p id="Dzv0"><strong>Shared/</strong> — библиотека бизнес-логики</p>
  <p id="aGcv">Папка &#x60;shared/&#x60; — это не “склад” общих файлов, а настоящая библиотека бизнес-типов и DTO, которые используются в нескольких модулях.  Здесь живут те сущности, которые отражают бизнес-реальность и нужны сразу в нескольких частях приложения.</p>
  <p id="xnuQ"><strong>Что кладём в shared:</strong></p>
  <p id="quuT">- DTO для пагинации (&#x60;PaginateDto&#x60;, &#x60;PaginatedResult&lt;T&gt;&#x60;)</p>
  <p id="9CFQ"><br />- Общие идентификаторы (&#x60;IdentifierDto&#x60;)</p>
  <p id="JG4l"><br />- Базовые бизнес-ответы (например, &#x60;SuccessResponseDto&#x60;, &#x60;ErrorResponseDto&#x60;)</p>
  <p id="Rf3u"></p>
  <p id="pE1E">-  Общие типы и интерфейсы, которые описывают бизнес-логику (например,      &#x60;UserRole&#x60;, &#x60;StatusEnum&#x60;)</p>
  <p id="eC9x"><br />- DTO для фильтрации, сортировки, поиска, если эти механизмы повторяются в разных модулях</p>
  <p id="mdtV"></p>
  <p id="sBZW"><strong>Зачем это нужно: <br /></strong>Когда бизнес-логика повторяется в разных модулях (например, пагинация нужна и для пользователей, и для проектов, и для заказов), мы не копируем код, а используем единый контракт. Это гарантирует согласованность API, облегчает поддержку фронта и ускоряет внедрение новых сущностей.</p>
  <p id="Np7A"></p>
  <p id="yagw"><strong>Пример:<br /></strong><code>// shared/dto/paginate.dto.ts<br />export class PaginateDto {<br />  page?: number = 1;<br />  limit?: number = 10;<br />}</code><br /><br /><code>// shared/dto/paginated-result.dto.ts<br />export interface PaginatedResult&lt;T&gt; {<br />  items: T[];<br />  total: number;<br />  page: number;<br />  limit: number;<br />}</code><br /></p>
  <p id="jHkf"></p>
  <p id="NRyW"><strong>Common/ — инфраструктурный слой</strong></p>
  <p id="RwGi">Папка &#x60;common/&#x60; — это технический фундамент приложения. Здесь лежит всё то, что не связано напрямую с бизнес-логикой, но обеспечивает стабильную работу, безопасность и удобство разработки.</p>
  <p id="BLVb"></p>
  <p id="A8es"><strong>Что кладём в common:</strong></p>
  <p id="0kZM">- Глобальные фильтры ошибок (&#x60;HttpExceptionFilter&#x60;)</p>
  <p id="5Bi3"><br />- Middleware (например, логирование, CORS, трекинг запросов)</p>
  <p id="SUJy"><br />- Глобальные константы (например, лимиты, дефолтные значения)</p>
  <p id="EaZW"><br />- Утилиты и хелперы (например, функции для работы с датами, генерация токенов, парсинг переменных окружения)</p>
  <p id="AN2o"><br />- Глобальные guards, interceptors, pipes</p>
  <p id="8gsZ"><br />- Базовые технические DTO (например, для health-check)</p>
  <p id="dmS3"></p>
  <blockquote id="TVKK"> <em>Всё, что касается инфраструктуры, технических аспектов и “склеивания” приложения, должно быть централизовано. Это позволяет быстро вносить изменения, не затрагивая бизнес-логику, и обеспечивает единые стандарты по всему проекту.</em></blockquote>
  <p id="Hrkk"></p>
  <p id="02FP"><strong>Пример:<br /></strong><br /><code>// common/filters/http-exception.filter.ts<br />@Catch(HttpException)<br />export class HttpExceptionFilter implements ExceptionFilter {<br />  // ...логика логирования и форматирования ошибок...<br />}</code><br /><br /><br /><code>// common/utils/get-env-var.ts<br />export function getEnvVar(key: string, fallback?: string): string {<br />  // ...логика получения переменной окружения с учётом контура...<br />}</code><br /></p>
  <p id="5Ys8"></p>
  <p id="yLvx"><strong>Философия: “Не дублируй, а делись”</strong></p>
  <p id="H7u6"></p>
  <p id="g81p">- Всё, что может понадобиться в нескольких местах — выносится в shared.<br />- Всё, что обеспечивает техническую инфраструктуру — в common.<br />- В модулях остаётся только то, что уникально для конкретного бизнес-домена.</p>
  <p id="I7j5"></p>
  <p id="gs2o"><strong>Преимущества такого подхода:</strong></p>
  <p id="oR60">- <strong>Единые стандарты:</strong> фронт и бэкенд всегда “говорят на одном языке”.</p>
  <p id="uwCn"><br />- <strong>Минимум дублирования: </strong>меньше багов, проще поддержка.</p>
  <p id="9bIe"><br />- <strong>Быстрый онбординг: </strong>новому разработчику легко понять, где искать нужный тип или утилиту.</p>
  <p id="gh5B"><br />- Г<strong>ибкость:</strong> легко расширять проект, не боясь “сломать” что-то в другом модуле.</p>
  <p id="K8y5"></p>
  <blockquote id="waAP">Чёткое разделение shared и common — это не формальность и не вопрос организации файловой системы. Это проявление зрелого инженерного мышления, осознанного подхода к переиспользованию и стратегической заботы о поддерживаемости и будущем продукта.  Придерживаясь этого принципа,  избавляетесь от хаоса и закладываете фундамент прозрачности кода <em>— и ваш код станет чище, а команда — эффективнее.</em></blockquote>
  <p id="aZuy"><br /><br /></p>
  <h2 id="3LeN">2. DTO-first API: безопасность, прозрачность, удобство</h2>
  <p id="F0Zr"></p>
  <h3 id="qdEB">2.1. Почему DTO-first?</h3>
  <p id="UWke"></p>
  <p id="jH6n">- <strong>Безопасность:</strong> наружу никогда не уходит entity, только DTO. Даже если в entity появится новое поле (например, “isAdmin”), оно не “утечёт” наружу случайно.</p>
  <p id="nmVW"><br />- <strong>Явная структура:</strong> фронтенд и документация всегда знают, что вернёт API. Swagger всегда актуален.</p>
  <p id="Ork6"><br />- <strong>Гибкость:</strong> можно легко менять структуру ответа, не трогая базу. Например, добавить computed-поле или убрать лишнее.</p>
  <p id="9ywO"></p>
  <h3 id="5I6p"><strong>2.2. Пример DTO для пользователя</strong></h3>
  <p id="6QBV"><br /><code>// src/modules/users/dto/user-response.dto.ts<br />import { ApiProperty } from &#x27;@nestjs/swagger&#x27;;</code></p>
  <p id="lMFI"><code>export class UserResponseDto {<br />  @ApiProperty({ example: 1 })<br />  id: number;</code></p>
  <p id="y6IY"><code>  @ApiProperty({ example: &#x27;user123&#x27; })<br />  username: string;<br />}</code><br /></p>
  <p id="rXJx"><strong>В сервисе:<br /></strong><br /><code>async findAll(): Promise&lt;UserResponseDto[]&gt; {<br />  const users = await this.usersRepository.find();<br />  return users.map(({ password, ...user }) =&gt; user as UserResponseDto);<br />}</code><br /><br /><strong>Примечание:</strong></p>
  <p id="1w6y"><em>Пароль никогда не попадёт наружу, даже если кто-то забудет про <code>exclude</code></em></p>
  <p id="Q0z3"></p>
  <p id="hHhn"><strong>DTO для параметров пагинации:</strong></p>
  <p id="WWrs"><br /><code>// src/shared/dto/paginate.dto.ts<br />import { ApiPropertyOptional } from &#x27;@nestjs/swagger&#x27;;<br />import { Type } from &#x27;class-transformer&#x27;;<br />import { IsPositive, Min } from &#x27;class-validator&#x27;;</code></p>
  <p id="e2Vt"><code>export class PaginateDto {<br />  @ApiPropertyOptional({ example: 1, default: 1 })<br />  @Type(() =&gt; Number)<br />  @IsPositive()<br />  page?: number = 1;</code></p>
  <p id="CUfr"><code>  @ApiPropertyOptional({ example: 10, default: 10 })<br />  @Type(() =&gt; Number)<br />  @Min(1)<br />  limit?: number = 10;<br />}</code><br /></p>
  <p id="q75Z"><strong>DTO для результата пагинации:<br /></strong><br /><code>// src/shared/dto/paginated-result.dto.ts<br />export interface PaginatedResult&lt;T&gt; {<br />  items: T[];<br />  total: number;<br />  page: number;<br />  limit: number;<br />}<br /></code></p>
  <p id="lgm4"><strong>Использование в сервисе:<br /></strong><br /><code>async findAllPaginated(page = 1, limit = 10): Promise&lt;PaginatedResult&lt;UserResponseDto&gt;&gt; {<br />  const [items, total] = await this.usersRepository.findAndCount({<br />    skip: (page - 1) * limit,<br />    take: limit,<br />    order: { id: &#x27;ASC&#x27; },<br />  });<br />  return {<br />    items: items.map(({ password, ...user }) =&gt; user as UserResponseDto),<br />    total,<br />    page,<br />    limit,<br />  };<br />}</code><br /></p>
  <p id="OCIL"><br /><strong>Примечание:<br /></strong><em>Пагинация становится стандартной и одинаковой для всех сущностей. Фронт всегда знает, что ожидать.</em></p>
  <p id="71Y2"></p>
  <p id="loOH"></p>
  <p id="Ggkc">Вот расширенная и более глубокая версия главы о логировании, с примерами, практическими советами и акцентом на инженерную культуру:</p>
  <p id="s1P7">---</p>
  <h2 id="KOVY">3. Логирование: не только для дебага</h2>
  <p id="7m0l"></p>
  <blockquote id="jwHr">Логирование — это не типа “выводить что-то в консоль”, чтобы отловить баг. Это полноценный инструмент управления проектом, который помогает видеть невидимое, анализировать поведение системы и принимать обоснованные решения.</blockquote>
  <p id="iUrS"></p>
  <p id="J8gr"><strong>Почему логирование — это важно</strong></p>
  <p id="upsh">- <strong>Аудит: </strong>Логи фиксируют все значимые действия в системе — кто, когда и что сделал. Это важно для расследования инцидентов, анализа истории изменений и соответствия требованиям безопасности.</p>
  <p id="g39e"><br />- <strong>Поиск и устранение ошибок: </strong>Хорошо структурированные логи позволяют быстро находить причину сбоя, даже если ошибка проявилась не сразу.<br /></p>
  <p id="Hz7z">- <strong>Мониторинг и алерты:</strong> Логи — основа для систем мониторинга (например, Sentry, ELK, Prometheus), которые могут автоматически оповестить команду о критических ошибках или аномалиях.</p>
  <p id="yTln"><br />- <strong>Аналитика и оптимизация:</strong> По логам можно анализировать производительность, выявлять узкие места, отслеживать частоту и причины ошибок, строить отчёты по бизнес-событиям.</p>
  <p id="u2W4"></p>
  <p id="iwuG"></p>
  <h3 id="jLep">Практика: как мы логируем</h3>
  <p id="G8lE"></p>
  <p id="Vta3">- Используем встроенный &#x60;Logger&#x60; из &#x60;@nestjs/common&#x60; — он интегрируется с экосистемой NestJS, поддерживает уровни логирования (log, error, warn, debug, verbose) и легко расширяется.</p>
  <p id="IfrG"><br />- Логируем не только ошибки, но и ключевые бизнес-события: создание пользователя, запуск важной задачи, смена статуса заказа и т.д.</p>
  <p id="2WNp"><br />- В фильтрах ошибок обязательно логируем stack trace — это ускоряет диагностику и помогает понять, где именно произошёл сбой.</p>
  <p id="KFWn"><br />- Для сложных сценариев используем контекст (context) — чтобы в логах всегда было понятно, из какого модуля или сервиса пришло сообщение.</p>
  <p id="9sQg"></p>
  <p id="9u8S"><strong>Пример:<br /></strong><br /><code>import { Logger } from &#x27;@nestjs/common&#x27;;</code></p>
  <p id="YLdz"><code>@Injectable()<br />export class UsersService {<br />  private readonly logger = new Logger(UsersService.name);</code></p>
  <p id="g4aU"><code>  async create(username: string, password: string): Promise&lt;UserResponseDto&gt; {<br />    this.logger.log(&#x60;Создание пользователя: ${username}&#x60;);<br />    try {<br />      // ... логика создания пользователя ...<br />      this.logger.log(&#x60;Пользователь успешно создан: ${username}&#x60;);<br />    } catch (error) {<br />      this.logger.error(&#x60;Ошибка при создании пользователя: ${username}&#x60;, error.stack);<br />      throw error;<br />    }<br />  }<br />}</code><br /></p>
  <p id="S1YX"><strong>Советы и best practices</strong></p>
  <p id="geZn">- <strong>Стандартизируйте формат логов:</strong> Используйте единый стиль сообщений, чтобы их было легко парсить и анализировать.</p>
  <p id="nGIX"><br />- <strong>Не логируйте чувствительные данные:</strong> Никогда не пишите в логи пароли, токены, персональные данные пользователей.</p>
  <p id="BOmQ"><br />- <strong>Используйте уровни логирования:</strong> Для обычных событий — &#x60;log&#x60;, для подозрительных — &#x60;warn&#x60;, для ошибок — &#x60;error&#x60;, для отладки — &#x60;debug&#x60;.</p>
  <p id="Au3I"><br />- <strong>Интегрируйте логи с внешними системами: </strong>Используйте централизованные системы сбора логов (например, ELK, Graylog, Sentry), чтобы не терять важную информацию и быстро реагировать на инциденты.</p>
  <p id="iy0Y"><br />- <strong>Добавляйте контекст: </strong>Указывайте, из какого модуля, сервиса или запроса пришёл лог — это ускоряет поиск нужной информации.</p>
  <p id="aJLA"></p>
  <blockquote id="Ddrd"><strong>Логирование</strong> —  не “дополнительная опция”, а неотъемлемая часть зрелого backend-проекта.  Грамотно выстроенная система логов превращает хаос в управляемую систему, помогает команде быстрее реагировать на проблемы, анализировать поведение пользователей и строить действительно надёжные сервисы.  </blockquote>
  <p id="gOp6"></p>
  <p id="HL1z"><strong>Инвестируйте время в логи — и они не раз спасут ваш проект.</strong></p>
  <p id="SPGJ"></p>
  <p id="EPPq">Вот более развернутая, строгая и профессионально оформленная версия раздела о валидации и обработке ошибок, с акцентом на инженерную культуру и безопасность:</p>
  <p id="zVGL">---</p>
  <h2 id="3fHt">4. Валидация и обработка ошибок: доверяй, но проверяй</h2>
  <p id="TPam"></p>
  <blockquote id="luiH">В современных backend-системах нет места случайностям: каждый входящий запрос проходит строгую валидацию, а любая ошибка обрабатывается так, чтобы обеспечить безопасность пользователя и стабильность всей платформы.</blockquote>
  <p id="Y9zR"></p>
  <p id="roO1">- <strong>Валидация данных: </strong>Для всех входных DTO мы используем возможности &#x60;class-validator&#x60;. Это гарантирует, что на уровень бизнес-логики никогда не попадут “грязные” или некорректные данные. Валидация происходит автоматически, а правила описываются декларативно прямо в классах DTO.</p>
  <p id="ikxK"></p>
  <p id="uoQY"><strong>Пример DTO с валидацией:<br /></strong><br /><code>import { IsString, MinLength } from &#x27;class-validator&#x27;;</code></p>
  <p id="cLGf"><code>export class CreateUserDto {<br />  @IsString()<br />  username: string;</code></p>
  <p id="XubA"><code>  @IsString()<br />  @MinLength(8)<br />  password: string;<br />}</code><br /><br />Такой подход устраняет ошибки на входе  и превращает API в динамично проверяемый и самоописательный интерфейс,  правила валидации становятся частью кода и документации одновременно. Это позволяет команде быстро внедрять изменения, интегрироваться без лишних согласований и строить надёжные сервисы с минимальными коммуникационными издержками.</p>
  <p id="Ly2O"></p>
  <p id="9CHS"><strong>Обработка ошибок: </strong>Все ошибки, возникающие в приложении, перехватываются глобальным фильтром. </p>
  <p id="Iz4f"></p>
  <p id="W6LJ"><strong>Мы реализуем собственный фильтр (&#x60;HttpExceptionFilter&#x60;), который:</strong></p>
  <p id="cRhx"><br />  - Логирует ошибку и stack trace для последующего анализа.</p>
  <p id="pmBn"><br />  - Формирует структурированный и предсказуемый ответ для клиента.</p>
  <p id="5XYQ"><br />  - Никогда не раскрывает внутренние детали реализации, стек вызовов или чувствительную информацию наружу.</p>
  <p id="boMo"></p>
  <p id="99AP"><strong>Пример глобального фильтра ошибок:</strong><br /><br /><code>@Catch(HttpException)<br />export class HttpExceptionFilter implements ExceptionFilter {<br />  private readonly logger = new Logger(HttpExceptionFilter.name);</code></p>
  <p id="ToO4"><code>  catch(exception: HttpException, host: ArgumentsHost) {<br />    // Логируем ошибку для внутреннего аудита<br />    this.logger.error(exception.message, exception.stack);</code></p>
  <p id="iRIT"><code>    // Формируем безопасный и понятный ответ для клиента<br />    const ctx = host.switchToHttp();<br />    const response = ctx.getResponse&lt;Response&gt;();<br />    const status = exception.getStatus();</code></p>
  <p id="bWvi"><code>    response.status(status).json({<br />      statusCode: status,<br />      message: exception.message,<br />      // Можно добавить поле errors для передачи детальной информации о валидации<br />    });<br />  }<br />}</code><br /></p>
  <p id="Yp8D"></p>
  <p id="z2vI"><strong>Почему это важно:  </strong><br />Пользователь всегда получает информативный, но безопасный ответ — без “500 Internal Server Error” и загадочных сообщений. Внутри команды мы сохраняем всю необходимую информацию для диагностики и исправления ошибок, не подвергая риску безопасность или приватность данных.</p>
  <blockquote id="N6TG">  Жёсткая валидация и централизованная обработка ошибок — это не просто “best practice”, а фундамент надёжности, безопасности и предсказуемости вашего backend-приложения.</blockquote>
  <p id="jc8R"></p>
  <p id="4Deu"></p>
  <h2 id="HnOt"> 5. Swagger-документация: всегда актуальна</h2>
  <p id="MUt3"></p>
  <h3 id="3UPF">Зачем нам это нужно?</h3>
  <p id="WOv9">Представьте: новый разработчик приходит в команду и ему нужно разобраться с API. Или клиент хочет интегрироваться с вашим сервисом. Что они видят? Если это куча непонятных эндпоинтов без объяснений - это путь к бесконечным вопросам и ошибкам.</p>
  <p id="ERH9">Swagger - это не  документация, это ваш способ общения с внешним миром. И как любое общение, оно должно быть понятным и приятным.</p>
  <p id="aquk"></p>
  <p id="pyF0"><strong>Основные принципы</strong></p>
  <ul id="IEZh">
    <li id="Kvg4">Полная документация: Каждый эндпоинт, DTO и модель должны быть полностью документированы с помощью Swagger-аннотаций</li>
    <li id="kThe">Автоматическая валидация: Документация проверяется автоматически при сборке проекта</li>
    <li id="oRz0">Интерактивность: Swagger UI предоставляет возможность тестирования API прямо из браузера</li>
    <li id="ViUZ">Версионирование: Поддержка версионирования API с отдельной документацией для каждой версии</li>
  </ul>
  <p id="oK22"></p>
  <p id="Cq2m"></p>
  <h3 id="7olR"><strong>Как мы это делаем?</strong></h3>
  <p id="W2Jl"></p>
  <p id="CVdy"><strong>1. Пишем документацию как код</strong></p>
  <p id="m236">Каждый <strong>DTO</strong> -  не просто класс, это история о том, что он делает:<br /><br /><strong><code>@ApiProperty({<br />  description: &#x27;Идентификатор пользователя в системе&#x27;,<br />  example: &#x27;507f1f77bcf86cd799439011&#x27;,<br />  required: true<br />})<br />userId: string;</code></strong><br /></p>
  <p id="hmPV">Каждый <strong>эндпоинт</strong> -  не просто метод, это инструкция:<br /><br /><code>@ApiOperation({<br />  summary: &#x27;Создание нового пользователя&#x27;,<br />  description: &#x27;Регистрирует нового пользователя в системе. Требуется подтверждение email.&#x27;<br />})<br />@ApiResponse({<br />  status: 201,<br />  description: &#x27;Пользователь успешно создан&#x27;,<br />  type: UserResponseDto<br />})</code></p>
  <p id="UpBy"><br /></p>
  <p id="5HuG"><strong>2. Делаем документацию живой</strong></p>
  <p id="mrMn"></p>
  <p id="nW0O">- <strong>Примеры из реальной жизни:</strong> вместо абстрактных значений показываем реальные данные</p>
  <p id="4xgf"><br />- <strong>Ошибки и их решения:</strong> описываем успешные сценарии и что делать, если что-то пошло не так</p>
  <p id="Dr3d"><br />- <strong>Секьюрность: </strong>объясняем, как работать с авторизацией, какие токены нужны и где их брать</p>
  <p id="uHES"></p>
  <p id="XVOB"><strong>3. Организуем информацию</strong></p>
  <p id="hMRh">Представьте, что вы заходите в библиотеку. Книги разложены по полкам, есть указатели, каталоги. Так же и с API:</p>
  <p id="Ry1M">- Группируем эндпоинты по смыслу (пользователи, платежи, отчеты)<br />- Добавляем теги для быстрой навигации<br />- Создаем иерархию: от общего к частному</p>
  <p id="dOdt"></p>
  <p id="yZYK"><strong>4. Автоматизируем процесс</strong></p>
  <p id="iGMB">Документация - это не разовая акция, а постоянный процесс:</p>
  <p id="iOe8">- В CI/CD проверяем, что документация соответствует коду<br />- При мердж-реквесте автоматически проверяем наличие описаний<br />- Генерируем клиентские SDK на основе документации</p>
  <p id="sWBH"></p>
  <h3 id="grPL">Что получаем в итоге?</h3>
  <p id="6tOS"></p>
  <p id="iIAW">1. <strong>Для команды:</strong><br />   - Меньше вопросов &quot;а как это работает?&quot;<br />   - Быстрее ввод новых разработчиков<br />   - Меньше ошибок при интеграции<br />   - Понятный интерфейс для тестирования API<br />   - Готовые примеры кода<br />   - Четкое понимание ограничений и возможностей</p>
  <p id="YNf8">2. Для бизнеса:<br />   - Меньше времени на поддержку<br />   - Более быстрая интеграция партнеров<br />   - Профессиональный имидж</p>
  <p id="4Emi"></p>
  <h3 id="FSh0">Советы из практики</h3>
  <p id="oper"></p>
  <p id="wjCo">1. <strong>Пишите для людей:</strong><br />   - Используйте простой язык<br />   - Добавляйте примеры из реальной жизни<br />   - Объясняйте неочевидные моменты</p>
  <p id="MWlo">2. <strong>Держите в актуальном состоянии:</strong><br />   - Сделайте обновление документации частью процесса разработки<br />   - Проверяйте актуальность при каждом изменении API<br />   - Собирайте обратную связь от пользователей</p>
  <p id="ZWlv">3. <strong>Делайте красиво:</strong><br />   - Добавьте логотип и фирменные цвета<br />   - Структурируйте информацию логично<br />   - Используйте форматирование для лучшей читаемости</p>
  <p id="LUbk"></p>
  <h3 id="vBP5">Важный совет:</h3>
  <p id="OKuy">Хорошая документация - это как хороший гид в незнакомом городе. Каждый перекресток (интеграция) снабжен понятными указателями, а все подводные камни (ограничения и особенности) заранее отмечены на карте. Хотите превратить вашу документацию из скучного справочника в увлекательный путеводитель? Стремитесь создавать документацию, которая не просто объясняет, а вдохновляет разработчиков на использование вашего API!</p>
  <p id="Eg1W"></p>
  <p id="ltJA"></p>
  <h2 id="haF1">6. Безопасность: не доверяй даже себе</h2>
  <p id="jYys"></p>
  <p id="8U6V">В современной разработке безопасность - это философия, которая должна пронизывать каждый аспект разработки. Давайте поговорим о том, как мы подходим к этому вопросу в нашем проекте.</p>
  <p id="EA2P">Основной принцип, которым мы руководствуемся - это принцип минимальных привилегий. Каждый компонент системы должен иметь доступ только к тем ресурсам, которые ему действительно необходимы для работы. Это касается как кода, так и инфраструктуры.</p>
  <p id="8nCo">Имея дело с  аутентификацией,  используй JWT с умом. Токены имеют ограниченное время жизни, регулярно обновляются, а их секреты хранятся в защищённом окружении. Но самое главное - мы никогда не доверяем клиенту полностью. Каждый запрос проходит через строгую валидацию и проверку прав доступа. Работа с паролями - это отдельная история. Мы хешируем их перед сохранением и используем современные алгоритмы с солью и итерациями. </p>
  <p id="Fkhe">Все секреты, ключи и конфигурации живут только в .env файлах, которые никогда не попадают в репозиторий. Для разработчиков мы предоставляем .env.example с безопасными тестовыми значениями.</p>
  <p id="6Kes">Но безопасность - это не только про код. Это про процессы и культуру разработки. Каждый пулл-реквест проходит проверку на безопасность, мы регулярно обновляем зависимости и проводим аудиты. Наши разработчики обучены распознавать потенциальные уязвимости и знают, как их избежать.</p>
  <p id="j3xz"></p>
  <p id="zdYL"></p>
  <p id="jGvH"><strong>Хранение паролей: </strong>только хеши, никогда исходные значения</p>
  <p id="ZrFu"></p>
  <p id="gUAY"> Давайте разберем, как это работает на практике:</p>
  <p id="OjvQ"><br /><code>@Injectable()<br />export class PasswordService {<br />  private readonly SALT_ROUNDS = 12;</code></p>
  <p id="yi4p"><code>  async hashPassword(password: string): Promise&lt;string&gt; {<br />    // Генерируем уникальную соль для каждого пароля<br />    const salt = await bcrypt.genSalt(this.SALT_ROUNDS);<br />    // Хешируем пароль с солью<br />    return bcrypt.hash(password, salt);<br />  }</code></p>
  <p id="YEgD"><code>  async verifyPassword(password: string, hash: string): Promise&lt;boolean&gt; {<br />    // Сравниваем введенный пароль с хешем из базы<br />    return bcrypt.compare(password, hash);<br />  }<br />}</code><br /></p>
  <p id="k73u">Когда пользователь регистрируется, мы сразу хешируем его пароль:</p>
  <p id="V8PC"><br /><code>@Injectable()<br />export class AuthService {<br />  async register(createUserDto: CreateUserDto) {<br />    const hashedPassword = await this.passwordService.hashPassword(createUserDto.password);<br />    <br />    const user = await this.userRepository.create({<br />      ...createUserDto,<br />      password: hashedPassword // Сохраняем только хеш<br />    });</code></p>
  <p id="ncb5"><code>    // Никогда не возвращаем пароль, даже хешированный<br />    const { password, ...safeUser } = user;<br />    return safeUser;<br />  }<br />}</code><br /></p>
  <p id="PtXv">Этот подход обеспечивает несколько уровней защиты:</p>
  <p id="Ibta">1. Даже если злоумышленник получит доступ к базе данных, он не сможет восстановить исходные пароли</p>
  <p id="hZj2"><br />2. Использование соли предотвращает атаки с использованием радужных таблиц</p>
  <p id="dXH9"><br />3. Современные алгоритмы хеширования: </p>
  <p id="VnSd"><strong>Argon2</strong> - победитель Password Hashing Competition 2015, предлагающий три варианта:</p>
  <ul id="8hRf">
    <li id="sogk">Argon2d - максимизирует устойчивость к GPU-атакам</li>
    <li id="hlgu">Argon2i - оптимизирован для защиты от side-channel атак</li>
    <li id="Xu17">Argon2id - гибридный подход, сочетающий преимущества обоих вариантов</li>
  </ul>
  <p id="lfcm"></p>
  <p id="xf0L"><strong>scrypt</strong> - алгоритм, специально разработанный для противодействия аппаратным атакам, требующий большого объема памяти для вычислений.</p>
  <p id="hMkn"></p>
  <p id="9vNK"><strong>PBKDF2</strong> (Password-Based Key Derivation Function 2) - широко используемый стандарт, поддерживающий различные хеш-функции и итерации.</p>
  <p id="wxxr"></p>
  <p id="PzJd"><strong>Balloon Hashing</strong> - современный алгоритм, использующий псевдослучайные перестановки памяти для создания &quot;вычислительного барьера&quot;.</p>
  <p id="AA9e"></p>
  <p id="OhBI">Важно отметить, что мы не только не храним пароли, но и не логируем их ни в каком виде. Даже в случае ошибок аутентификации мы записываем только факт попытки, без каких-либо деталей о введенных данных.</p>
  <p id="szr8"></p>
  <p id="8du6"><strong>При аутентификации мы сравниваем хеш введенного пароля с хешем в базе:</strong></p>
  <p id="KcEX"><code>@Injectable()<br />export class AuthService {<br />  async hashPassword(password: string): Promise&lt;string&gt; {<br />    const salt = await bcrypt.genSalt(12);<br />    return bcrypt.hash(password, salt);<br />  }</code></p>
  <p id="NvtW"><code>  async validateUser(email: string, password: string) {<br />    const user = await this.usersService.findByEmail(email);<br />    if (!user) return null;<br />    <br />    const isValid = await Argon2.verify(user.password, pass);<br />    if (!isValid) return null;<br />    <br />    // Никогда не возвращаем пароль<br />    const { password: _, ...safeUser } = user;<br />    return safeUser;<br />  }<br />}</code></p>
  <p id="Tkao">Этот подход к хранению паролей является отраслевым стандартом и обеспечивает надежную защиту данных пользователей даже в случае компрометации базы данных.<br /></p>
  <p id="hEP1"><br /><br /></p>
  <p id="0Kso">## Защита эндпоинтов</p>
  <p id="fx1j">Защищенные маршруты используют кастомные guards и декораторы:</p>
  <p id="k4gt"><br /><code>@UseGuards(JwtAuthGuard, RolesGuard)<br />@Roles(UserRole.ADMIN)<br />@Controller(&#x27;admin&#x27;)<br />export class AdminController {<br />  @Get(&#x27;users&#x27;)<br />  async getUsers() {<br />    // Только админы могут получить доступ<br />  }<br />}</code><br /></p>
  <p id="pAlq"></p>
  <p id="OQr8"><strong> Практические рекомендации:</strong></p>
  <p id="FcWj">1. <strong>Работа с секретами</strong><br />   - Используйте &#x60;.env&#x60; для всех конфиденциальных данных<br />   - Регулярно ротируйте ключи и пароли<br />   - Храните разные секреты для разных окружений</p>
  <p id="Nd0u">2. <strong>Защита от атак</strong><br />   - Внедрите rate limiting<br />   - Настройте CORS политики<br />   - Используйте helmet для HTTP-заголовков</p>
  <p id="JT8n">3. <strong>Процессы разработки</strong><br />   - Включайте проверку безопасности в CI/CD<br />   - Проводите регулярные аудиты кода<br />   - Обучайте команду основам безопасностиё</p>
  <p id="T5di"></p>
  <h3 id="VjPL">Реальный пример защиты API:</h3>
  <p id="QWcw"><br /><code>@Controller(&#x27;api/v1&#x27;)<br />export class SecureController {<br />  @UseGuards(JwtAuthGuard)<br />  @UseInterceptors(ClassSerializerInterceptor)<br />  @Get(&#x27;user/profile&#x27;)<br />  async getProfile(@CurrentUser() user: User) {<br />    // Сериализуем только безопасные поля<br />    return new UserResponseDto(user);<br />  }</code></p>
  <p id="0mFy"><code>  @UseGuards(RolesGuard)<br />  @Roles(UserRole.ADMIN)<br />  @Post(&#x27;admin/users&#x27;)<br />  async createUser(@Body() createUserDto: CreateUserDto) {<br />    // Проверяем права и валидируем данные<br />    return this.userService.create(createUserDto);<br />  }<br />}</code><br /></p>
  <blockquote id="iO8F">Безопасность - это постоянный процесс улучшения и адаптации. не достаточно просто следовать лучшим практикам, следует  развивать их и адаптировать под реальные нужды. Каждый день мы учимся чему-то новому и применяем эти знания для защиты наших систем и данных пользователей.</blockquote>
  <p id="LIjs"></p>
  <p id="qU4i"></p>
  <p id="Dxth"></p>
  <h2 id="dCEE">7. Тесты: не только для CI</h2>
  <p id="UtHr"></p>
  <p id="QbN2">В современной разработке тестирование эволюционировало из простой проверки кода в мощный инструмент обеспечения качества и надежности системы. Давайте посмотрим, как мы реализуем современный подход к тестированию.</p>
  <p id="w7lS"></p>
  <p id="qMmt">- Для всех сервисов и контроллеров пишем unit- и e2e-тесты.<br />- Тесты проверяют не только happy path, но и ошибки, edge cases.<br />- Покрытие тестами — не самоцель, а инструмент уверенности в коде.</p>
  <p id="WZqF"></p>
  <p id="Mw7W">Пример современного подхода  к написанию тестов:</p>
  <p id="J4CN"><br /><code>import { Test, TestingModule } from &#x27;@nestjs/testing&#x27;;<br />import { faker } from &#x27;@faker-js/faker&#x27;;<br />import { PrismaService } from &#x27;@prisma/client&#x27;;<br />import { UserService } from &#x27;./user.service&#x27;;<br />import { createMock } from &#x27;@golevelup/ts-jest&#x27;;<br />import { mockDeep } from &#x27;jest-mock-extended&#x27;;</code></p>
  <p id="VweI"><code>describe(&#x27;UserService&#x27;, () =&gt; {<br />  let service: UserService;<br />  let prisma: DeepMockProxy&lt;PrismaService&gt;;</code></p>
  <p id="MyOf"><code>  beforeEach(async () =&gt; {<br />    prisma = mockDeep&lt;PrismaService&gt;();<br />    <br />    const module: TestingModule = await Test.createTestingModule({<br />      providers: [<br />        UserService,<br />        {<br />          provide: PrismaService,<br />          useValue: prisma,<br />        },<br />      ],<br />    }).compile();</code></p>
  <p id="BlLg"><code>    service = module.get(UserService);<br />  });</code></p>
  <p id="JiUk"><code>  it(&#x27;should create user with valid data&#x27;, async () =&gt; {<br />    const userData = {<br />      email: faker.internet.email(),<br />      password: faker.internet.password({ length: 12 }),<br />    };</code></p>
  <p id="R30G"><code>    prisma.user.create.mockResolvedValue({<br />      id: faker.string.uuid(),<br />      ...userData,<br />      createdAt: faker.date.past(),<br />      updatedAt: faker.date.recent(),<br />    });</code></p>
  <p id="jdzh"><code>    const result = await service.create(userData);<br />    <br />    expect(result).toMatchObject({<br />      email: userData.email,<br />    });<br />    expect(result.password).toBeUndefined();<br />  });<br />});</code><br /></p>
  <p id="034w"></p>
  <p id="BL52"></p>
  <p id="sHI0"><strong>Интеграция с  CI/CD</strong></p>
  <p id="Z1r9"><br /><code># .github/workflows/test.yml<br />name: Modern Test Pipeline<br />on: [push, pull_request]</code></p>
  <p id="ir93"><code>jobs:<br />  test:<br />    runs-on: ubuntu-latest<br />    strategy:<br />      matrix:<br />        node-version: [18.x, 20.x]<br />    steps:<br />      - uses: actions/checkout@v3<br />      - name: Use Node.js ${{ matrix.node-version }}<br />        uses: actions/setup-node@v3<br />        with:<br />          node-version: ${{ matrix.node-version }}<br />          cache: &#x27;npm&#x27;<br />      <br />      - name: Install dependencies<br />        run: npm ci<br />        <br />      - name: Run unit tests<br />        run: npm run test:unit<br />        env:<br />          NODE_ENV: test<br />          <br />      - name: Run e2e tests<br />        run: npm run test:e2e<br />        env:<br />          NODE_ENV: test<br />          TEST_DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}<br />          <br />      - name: Run mutation tests<br />        run: npm run test:mutation<br />        <br />      - name: Upload coverage<br />        uses: codecov/codecov-action@v3<br />        with:<br />          token: ${{ secrets.CODECOV_TOKEN }}<br />          <br />      - name: Performance test<br />        run: npm run test:performance</code><br /></p>
  <p id="jkXT"></p>
  <p id="pvIV">Современное тестирование - это комплексный подход к обеспечению качества, включающий:<br />- Автоматизированное тестирование на разных уровнях<br />- Мониторинг производительности<br />- Проверку контрактов API<br />- Тестиование мутаций <br />- Интеграцию с современными инструментами CI/CD</p>
  <p id="x5Ry">Это позволяет  находить ошибки и предотвращать их появление, обеспечивая высокое качество кода и надежность системы.<br /></p>
  <p id="3uNS"></p>
  <h2 id="S4qi">8. Docker и окружение: dev ≠ prod</h2>
  <p id="6xne"></p>
  <h3 id="hwOa">8.1. Три контура — dev, stage, prod</h3>
  <p id="sUhi"></p>
  <p id="Q7qJ"><strong>Используем default + три docker-compose файла:</strong></p>
  <p id="3pmH"><br />  <strong>docker-compose.yml</strong> — базовый, описывает сервисы.</p>
  <p id="eKgJ"><br />  <strong>docker-compose.dev.yml</strong> — dev-override: монтирует исходники, пробрасывает порты, использует dev-окружение.</p>
  <p id="JgGy"><br />  <strong>docker-compose.stage.yml</strong> и &#x60;docker-compose.prod.yml&#x60; — для stage и prod, с разными портами, переменными, настройками безопасности.</p>
  <p id="LCEU"></p>
  <p id="GzAR"></p>
  <p id="ktji"><strong>Пример:<br /></strong><br /><code># docker-compose.yml<br />services:<br />  app:<br />    build:<br />      context: .<br />      dockerfile: Dockerfile<br />    depends_on:<br />      - db<br />    networks:<br />      - gprod-network<br />  db:<br />    image: postgres:13<br />    volumes:<br />      - pgdata:/var/lib/postgresql/data<br />    networks:<br />      - gprod-network</code></p>
  <p id="Amry"><code>volumes:<br />  pgdata:</code></p>
  <p id="GRaZ"><code>networks:<br />  gprod-network:<br />    driver: bridge</code><br /></p>
  <p id="8ie4"><br /><code># docker-compose.dev.yml<br />services:<br />  app:<br />    env_file:<br />      - .env<br />    environment:<br />      - NODE_ENV=development<br />      - DATABASE_HOST=db<br />      - DATABASE_PORT=5432<br />      - DATABASE_USER=${DEV_DATABASE_USER}<br />      - DATABASE_PASSWORD=${DEV_DATABASE_PASSWORD}<br />      - DATABASE_NAME=${DEV_DATABASE_NAME}<br />      - JWT_SECRET=${DEV_JWT_SECRET}<br />    ports:<br />      - &quot;3000:3000&quot;<br />    volumes:<br />      - .:/app<br />      - /app/node_modules<br />    depends_on:<br />      - db<br />    networks:<br />      - gprod-network</code></p>
  <p id="vQUE"><code>  db:<br />    image: postgres:13<br />    environment:<br />      - POSTGRES_USER=${DEV_DATABASE_USER}<br />      - POSTGRES_PASSWORD=${DEV_DATABASE_PASSWORD}<br />      - POSTGRES_DB=${DEV_DATABASE_NAME}<br />    ports:<br />      - &quot;5432:5432&quot;<br />    networks:<br />      - gprod-network</code><br /></p>
  <p id="nyP0"><strong>Пояснение: <br /></strong>- Для каждого окружения свои переменные, свои порты, свои тома.<br />- В dev-контуре код монтируется внутрь контейнера для hot-reload.<br />- В prod — только собранный dist, никакого доступа к исходникам.</p>
  <p id="bC0k"></p>
  <p id="ypS6"></p>
  <h3 id="PU1h">8.2. Работа с переменными окружения</h3>
  <p id="gydO"></p>
  <p id="22LL">- Используем утилиту <strong>getEnvVar</strong> для гибкой работы с переменными окружения с учётом контура (dev, stage, prod).</p>
  <p id="LWF4"><br />- Все секреты и ключи — только в <strong>.env</strong>, не коммитим в репозиторий.</p>
  <p id="Z8Ak"></p>
  <p id="bAax"><strong>Пример</strong>:<br /><br /><code>export function getEnvVar(configService: ConfigService, key: string, fallback?: string) {<br />  let env = (configService.get&lt;string&gt;(&#x27;NODE_ENV&#x27;) || process.env.NODE_ENV || &#x27;development&#x27;).toLowerCase();<br />  if (env.startsWith(&#x27;dev&#x27;)) env = &#x27;dev&#x27;;<br />  else if (env.startsWith(&#x27;prod&#x27;)) env = &#x27;prod&#x27;;<br />  else if (env.startsWith(&#x27;stag&#x27;)) env = &#x27;stage&#x27;;<br />  env = env.toUpperCase();<br />  return (<br />    configService.get&lt;string&gt;(&#x60;${env}_${key}&#x60;) ||<br />    configService.get&lt;string&gt;(key) ||<br />    fallback<br />  );<br />}</code><br /><br />Это позволяет легко деплоить один и тот же код в разные окружения без правок.</p>
  <p id="U7pU"></p>
  <p id="arpQ"></p>
  <h3 id="2Yxb"> Health-check и базовые контроллеры</h3>
  <p id="nnmS"></p>
  <p id="4WvP">- В проекте реализованы базовые контроллеры (например, app.controller.ts) для проверки работоспособности сервиса.</p>
  <p id="0fh6"><br />- DTO даже для простых ответов (например, HelloResponse) — это удобно для автотестов, мониторинга и Swagger.</p>
  <p id="H6U2"></p>
  <p id="lbWy"></p>
  <h2 id="nNco">9. Пошаговый чек-лист для внедрения best practices</h2>
  <p id="TNBI"></p>
  <p id="RoBX"><strong>1. Везде используем DTO, entity наружу не возвращаем.**<br />2. Пагинация и идентификаторы — только через shared/dto.**<br />3. Логирование — через Logger, логируем всё важное.**<br />4. Валидация — через class-validator, ошибки — через фильтр.**<br />5. Swagger — для всех DTO и эндпоинтов.**<br />6. Безопасность — не возвращаем пароли, используем guards.**<br />7. Тесты — для всего, что может сломаться.**<br />8. Docker — для всех окружений, переменные — через .env.**<br />9. Рефакторинг — всё общее в shared, всё техническое в common.**<br />10. Проверяем, что проект собирается, тесты проходят, Swagger актуален.**<br />11. Health-check эндпоинты — для мониторинга и CI/CD.**<br />12. Паттерн “конструктор с partial” — для entity.**<br />13. Гибкая работа с конфигами через ConfigService и утилиты.**</strong></p>
  <p id="MPnh">---</p>
  <h2 id="kIkj"></h2>
  <h3 id="Jfkw">Бонус: советы из реального опыта</h3>
  <p id="3GTl"></p>
  <p id="rj2r"><strong>- Не бойтесь удалять и переписывать код.  <br /></strong>  Лучший код — тот, который легко удалить или заменить.</p>
  <p id="Zrtz"><br /><strong>- Документируйте не только API, но и архитектурные решения. <br /></strong>  Почему выбрали именно такой подход? Это поможет новичкам и будущим вам.</p>
  <p id="Guzd"><br /><strong>- Внедряйте CI/CD с самого начала.<br /></strong>  Даже простая проверка тестов и линтера в pull request спасёт от многих багов.</p>
  <p id="e2qG"><br /><strong>- Общайтесь с командой.<br /></strong>  Лучшие архитектурные решения рождаются в диалоге.</p>
  <p id="iDF4"><br /><strong>- Не забывайте про мониторинг и алерты. <br /></strong>  Логирование — это хорошо, но оповещения о сбоях — ещё лучше.</p>
  <p id="SSh3"></p>
  <p id="ScdS"></p>
  <h2 id="EMNO">Финал</h2>
  <p id="eVMM"></p>
  <p id="XeEy">Мы построили backend, который:<br />- легко поддерживать и расширять,<br />- безопасен и прозрачен для команды,<br />- удобен для фронта и мобильных приложений,<br />- легко масштабируется и деплоится в любое окружение.</p>
  <blockquote id="NxT3">Этот опыт — результат десятков итераций, исправления ошибок, внедрения best practices и постоянного рефакторинга.  Следуйте этим принципам — и ваш проект будет радовать и команду, и бизнес!</blockquote>
  <p id="LgNZ"></p>
  <p id="fo3L">Добро пожаловать в будущее ! <br /></p>

]]></content:encoded></item></channel></rss>