Strona główna / Artykuły

Nauczanie Claude Code Twojego monorepo w NestJS: routowanie, zasady i umiejętności

Naucz Claude Code monorepo w NestJS: plik CLAUDE.md, zasady, umiejętności oraz uprawnienia, które umieszczają kod we właściwym serwisie i zapewniają przestrzeganie konwencji zespołu.

Wydawca: REACTAPP.TOP20 min czytania4492 słów

Źródła: Claude Code for NestJS Monorepos: A Practical Setup Guide

Jeśli zadasz niekonfigurowanemu asystentowi AI prośbę typu „dodaj status widoczny dla klienta do rezerwacji” w dużym monorepo NestJS, wynik będzie przewidywalny: zmiana trafi do niewłaściwego mikrosłużby, komunikat o błędzie to sztywno zdefiniowana ciąg znaków, a dostęp jest kontrolowany przez user.role === 'admin'. Model nie zawodzi z powodu TypeScript – on jedynie zgaduje decyzje podjęte dawno temu przez twoją zespół, który nigdy ich nie zapisał w miejscu, gdzie model mógłby je odczytać. Ten przewodnik pokazuje, jak zastąpić to zgadywanie kontekstem kontrolowanym wersją, wykorzystując cztery elementy budulcowe oferowane przez Claude Code: CLAUDE.md, reguły, umiejętności oraz ustawienia uprawnień. Na końcu będziesz miał strukturę, którą możesz zapisać w swoim repozytorium, dzięki czemu każdy inżynier oraz każda sesja AI będzie korzystać z tych samych konwencji.

Jeśli szukasz ogólnych wskazówek dotyczących tworzenia samego pliku z informacjami, zapoznaj się z naszym przewodnikiem dotyczącym skutecznego CLAUDE.md. Ten artykuł koncentruje się na tym, co zmienia się, gdy bazą kodu jest wielosłużbowy backend NestJS.

Wersja skrócona

Dla czytelników, którzy chcą najpierw listę kontrolną, a dopiero potem wyjaśnienia:

  1. Zainstaluj Claude Code (na przykład za pomocą npm install -g @anthropic-ai/claude-code) i zaloguj się raz, uruchamiając claude.
  2. Stwórz plik .claude/CLAUDE.md w katalogu głównym, który wyjaśnia, która usługa zarządza danymi, jaki jest kolejny krok w procesie obsługi żądania oraz jaką informację zwraca każdy punkt końcowy.
  3. Dodaj katalog .claude/rules/ z zasadami zakazującymi: brak zapytań w pętlach, brak sprawdzania nazw ról oraz brak importów względnych.
  • Dodaj katalog .claude/skills/ zawierający szczegółowe procedury i szablony kodu dla zadań, które wykonywane są wielokrotnie, takich jak tworzenie punktów końcowych.
  • Dodaj plik .claude/settings.json, aby z góry zatwierdzić bezpieczne polecenia i zablokować te destruktywne.
  • Zapisz cały katalog .claude/, aby konfiguracja mogła być udostępniana i sprawdzana podobnie jak kod.
  • Dlaczego monorepo w NestJS myli asystentów AI

    W dużym kodzie NestJS poprawność kodu często zależy od konwencji, których nie ujawnia żaden pojedynczy plik. Dziesiątki modułów, setki DTO-ów, niestandardowe mechanizmy ochronne, intercepcje, filtry wyjątków oraz adaptery HTTP tworzą prawdziwą niejednoznaczność. Gdy nic nie rozwiązuje tej niejednoznaczności, model ucieka się do sensownych domyślnych wartości, a te rzadko odpowiadają specyfice danej grupy.

    Typowe objawy niekonfigurowanego setupu:

    • Metoda usługi komunikuje się bezpośrednio z bazą danych, zamiast korzystać z warstwy repositoria.
    • Wiadomości o błędach to literale ciągu znaków, a nie wpisy z pliku konstant ErrorMessages.
    • W folderze operator-service tworzony jest nowy plik, mimo że logika rezerwacji należy do api-service.
    • Uprawnienia są sprawdzane za pomocą user.role === 'admin', zamiast wykorzystania PermissionCodes.
    • Dane debugowe są wyświetlane za pomocą console.log, zamiast strukturyzowanego narzędzia do logowania.
    • Importy odbywają się poprzez ścieżki typu ../../common/utils, zamiast użycia aliasu ścieżki src/.

    Główną przyczyną jest brak wiedzy specyficznej dla zespołu

    Żaden z tych problemów nie jest błędem modelu. Podczas sesji asystent zna tylko pliki, które otworzył. Nie ma możliwości dowiedzenia się, że zespół celowo usunął RolesGuard, że opcja synchronize w TypeORM musi pozostać na wartości false, lub że usługi komunikują się ze sobą wyłącznie za pośrednictwem adapterów HTTP. Ta wiedza znajduje się w umysłach inżynierów oraz w starych tematach pull requestów.

    Rozwiązaniem jest zapisanie tej wiedzy w plikach, które ładuje narzędzie, oraz dopasowanie każdego rodzaju wiedzy do odpowiedniego mechanizmu:

    • CLAUDE.md zawiera informacje o architekturze, odpowiedzialności za usługi oraz konwencjach. Jest on automatycznie ładowany na początku każdej sesji.
    • Zasady określają zakazy i niezmienniki, które powinny kształtować każdy fragment generowanego kodu.
  • Umiejętności obejmują wieloetapowe procesy pracy i szablony, które są wykorzystywane tylko wtedy, gdy pojawia się odpowiadające im zadanie.
  • Struktura repozytorium przyjęta w tym przewodniku

    Pomaga ustalić konkretną strukturę przed napisaniem jakiejkolwiek konfiguracji. Przykładowy backend składa się z czterech usług oraz pojedynczej ścieżki .claude/ w korzeniu repozytorium:

    my-backend/
    ├── .claude/                  # Everything Claude needs to operate well
    │   ├── CLAUDE.md             # Root-level instructions (always loaded)
    │   ├── rules/                # Enforced coding constraints
    │   ├── skills/               # Custom slash commands with deep context
    │   └── settings.json         # Command permissions
    ├── services/
    │   ├── api-service/          # NestJS consumer API (port 5001)
    │   ├── operator-service/     # NestJS operator/vendor API (port 5002)
    │   ├── chat-service/         # Express + Socket.IO (JavaScript, port 5005)
    │   └── job-service/          # Scheduled jobs, no HTTP server
    ├── db/migration/             # Flyway migrations per schema
    └── package.json              # Root: only husky + lint-staged
    

    Każda usługa to samodzielna aplikacja z własnym plikiem package.json, tsconfig.json oraz wynikami kompilacji. Należy zauważyć, że jedna z nich, chat-service, jest zwykłym JavaScriptem działającym na Express i Socket.IO, natomiast job-service w ogóle nie posiada serwera HTTP. Ta heterogeniczność jest dokładnie tym rodzajem informacji, której asystent nie może wywnioskować z pojedynczego pliku.

    W każdej usłudze NestJS moduły funkcjonalne mają tę samą trójwarstwową strukturę:

    src/modules/booking/
    ├── booking.module.ts
    ├── controllers/    # HTTP routing only, no logic
    ├── services/       # Business logic and orchestration
    ├── repositories/   # TypeORM queries only
    └── dtos/           # Request/response shapes, validation
    

    Kontrolery kierują żądaniami HTTP i nic więcej, usługi zawierają logikę biznesową oraz mechanizmy orkiestracji, a repozytoria zawierają zapytania TypeORM. Jeśli asystent nigdy nie został poinformowany o takim podziale, chętnie go zniekształci: usługa wyśle zapytania lub kontroler bezpośrednio wezwie repozytorium. Dokumentowanie warstw pozwala zachować te granice.

    Krok 1: instalacja i uwierzytelnianie

    Instalacja polega na dodaniu jednego globalnego pakietu, a następnie na jednorazowym logowaniu się w przeglądarce:

    npm install -g @anthropic-ai/claude-code
    claude   # opens a browser for one-time OAuth
    

    Po uwierzytelnieniu claude można uruchomić z dowolnego katalogu. Każdy programista instaluje go i loguje się na swoim komputerze; nie ma żadnego wspólnego klucza zespołowego do rozpowszechniania. Opcje instalacji zmieniają się z czasem, więc sprawdź aktualną dokumentację Claude Code, jeśli ścieżka npm nie jest już zalecana.

    Jeden szczegół ma kluczowe znaczenie szczególnie w monorepo. Konfiguracja projektu jest pobierana względem korzenia repozytorium, więc instrukcje znajdujące się w katalogu .claude/ obowiązują wszędzie tam, gdzie uruchamiasz sesję. Gdy twoja praca odbywa się wyłącznie w jednej usłudze, uruchom Claude z wnętrza tego katalogu usług, np. services/api-service/, aby wyszukiwania i polecenia działały z odniesieniem do odpowiedniego pakietu.

    Krok 2: napisz dokumentację CLAUDE.md

    CLAUDE.md to plik o największym wpływie, ponieważ jest to kontekst, który asystent czyta przed wszystkim innym. Napisz go w taki sposób, jakbyś informował doświadczonego inżyniera podczas jego pierwszego dnia pracy: zakładaj ogólną kompetencję i wyjaśnij tylko to, co jest specyficzne dla twojego systemu. W monorepo sensowne jest podzielenie na dwa poziomy:

    1. Korzeniowy plik .claude/CLAUDE.md dotyczący odpowiedzialności za usługę, wspólnych konwencji oraz procesu obsługi zapytań.
  • A services/{service}/CLAUDE.md dla każdej usługi, zawierający szczegóły dotyczące wzorów modułowych, autoryzacji oraz iniekcji zależności, które mają zastosowanie wyłącznie tam.
  • Plik główny: spraw, by odpowiedzialność za usługę była decyzją, a nie domysłem

    Najdroższy błąd w repozytorium z wieloma usługami to umieszczanie kodu w niewłaściwym miejscu – przeszkażę lokalną weryfikację i pojawi się dopiero później jako naruszenie granic. Plik główny zapobiega temu dzięki tabeli routingu oraz wyraźnemu wykazowi zabronionych wzorów. Oto przykład tego, co może zawierać taki plik:

    ## Monorepo StructureFour services under `services/`, each independently runnable.
    Run all commands from inside the service directory.| Service            | Port | Stack                    | Purpose              |
    | ------------------ | ---- | ------------------------ | -------------------- |
    | `api-service`      | 5001 | NestJS + TypeScript      | Consumer-facing API  |
    | `operator-service` | 5002 | NestJS + TypeScript      | Operator/vendor mgmt |
    | `chat-service`     | 5005 | Express + Socket.IO + JS | Real-time messaging  |
    | `job-service`      | —    | Node.js + TypeScript     | Scheduled jobs       |## Which Service Owns What- **operator-service** — if the data describes _what an experience is_:
      package definitions, pricing, taxes, availability, operator accounts.
    - **api-service** — if the data describes _a consumer's interaction_:
      bookings, users, auth, trip planning, payments, notifications.| Task involves...                   | Service                            |
    | ---------------------------------- | ---------------------------------- |
    | Package categories, pricing, slots | operator-service                   |
    | Booking status, user profiles      | api-service                        |
    | Stripe payments (consumer side)    | api-service                        |
    | Cross-service booking flow         | Start in api-service → IMS adapter |## Global Request Pipeline (in order)1. `TraceContextMiddleware` — sets `correlationId` in `AsyncLocalStorage`
    2. `ValidationPipe` — whitelist mode, flattens DTO errors
    3. `LoggerInterceptor` — logs request/response
    4. `TransformInterceptor` — wraps return value in the standard shape
    5. Exception filters (`HttpExceptionFilter`, `TypeOrmExceptionFilter`, …)## Standard Response ShapeControllers return `{ data, meta }`; `TransformInterceptor` wraps it:
    return { data: { items }, meta: { path: request.path } };
    // → { success: true, data: { items }, meta: { path }, error: null }## Cross-Service Communicationapi-service NEVER touches operator-service's database. All reads/writes go
    through HTTP adapters in `src/common/adapters/` (they handle OAuth refresh and
    trace propagation). Need operator data? Call the adapter, never the DB.## What Claude Must Never Do- Use `console.log` → use `logMessage()` from `src/common/utils/logger`
    - Check `user.role` → use `PermissionCodes` + `@Permissions()`
    - Query the DB inside a loop → use bulk queries (`IN`, `JOIN`)
    - Use relative `../../` imports → use the `src/` alias
    - Set TypeORM `synchronize: true` → Flyway manages all schema changes
    

    Kilka części tego pliku wymaga uwagi. Sekcja dotycząca własności jest sformułowana jako test („Czy dane opisują to, czym jest doświadczenie, czy interakcja konsumenta z nim?”) zamiast jako lista katalogów, co umożliwia asystentowi klasyfikację zapytań, których nigdy wcześniej nie widział. Sekcja dotycząca procesu przetwarzania wymienia middleware, rury przepływu danych, interceptyory i filtry w kolejności wykonywania, dzięki czemu generowany kod nie powtarza tego, co już robi TransformInterceptor. Sekcja dotycząca usług międzyserwisowych jasno stwierdza, że api-service nigdy nie ma dostępu do bazy danych operatora i musi korzystać z adapterów znajdujących się w katalogu src/common/adapters/.

    To, co odróżnia przydatny plik CLAUDE.md od słabego:

    • Zasady decyzyjne są lepsze od drzew plików. Zasada taka jak „dane opisujące, czym jest dana funkcjonalność, powinny znajdować się w operator-service” rozwiązuje niejednoznaczności bez konieczności zadawania pytań wyjaśniających.
    • Określ strukturę odpowiedzi. Bez tego każdy kontroler pakuje swoje własne odpowiedzi, co powoduje chaos w ich organizacji.
    • Wyjaśnij dokładnie, co jest zabronione. „Nigdy nie używaj console.log” jest łatwiejsze do wdrożenia niż „Używaj naszego narzędzia do logowania”.
    • Zapisuj celowe usunięcia. Jeśli usunąłeś RolesGuard lub wyłączyłeś synchronize, napisz o tym i podaj powód. W przeciwnym razie asystent może je ponownie wprowadzić, właśnie dlatego, że wyglądają jak standardowe rozwiązanie w NestJS.

    Pliki na poziomie usług: głębokość potrzebna tylko jednej usłudze

    Detalie, które mają znaczenie tylko w obrębie jednej usługi, trafiają do jej własnego pliku. W przypadku api-service chodzi tu o warstwowanie, autoryzację, tokeny DI oraz zadania w tle:

    ## Module Architecture — never mix layerscontrollers/ HTTP routing only — call service, return data
    services/ Business logic — orchestrate repos, no direct DB calls
    repositories/ TypeORM queries only — accept EntityManager for transactions## Authentication- `JwtAuthGuard` validates Bearer tokens, attaches `UserRequest`
    - `PermissionsGuard` reads `@Permissions([PermissionCodes.X])`
    - Never use RolesGuard or check role names — removed intentionally## Dependency InjectionRegister services by interface token, and inject by token, not class:
    { provide: SERVICE_INTERFACE.BOOKING_SERVICE, useClass: BookingService }## Background JobsBullMQ + Redis. Processors live in `src/modules/{module}/processors/`.
    Enqueue via the module's service — never from a controller.
    

    Dwa aspekty mogą być łatwo błędnie zrozumiane bez odpowiednich wskazówek. Po pierwsze, dostawcy są rejestrowani i injectowani za pomocą tokenów interfejsu, takich jak SERVICE_INTERFACE.BOOKING_SERVICE, a nie według klasy, więc generowany konstruktor, który bezpośrednio injectuje BookingService, mimo że się skompiluje, naruszy ustalone wzorce projektowe. Po drugie, zadania BullMQ są umieszczane w kolejce wyłącznie z usługi modułu, nigdy z kontrolera, a procesory znajdują się w katalogu src/modules/{module}/processors/. Unikanie umieszczania tych informacji w pliku głównym oznacza, że nie wpływają one na kontekst podczas pracy w chat-service.

    Krok 3: dodanie zasad dotyczących sytuacji, które nigdy nie mogą się zdarzyć

    CLAUDE.md opisuje, jak działa system, natomiast reguły określają, czego nie może zawierać kod wygenerowany. Przydatnym modelem mentalnym jest konfiguracja ESLint przeznaczona do obsługi wyników generowanych przez AI. Reguły to pliki Markdown znajdujące się w katalogu .claude/rules/.

    Prosty sposób na decydowanie, gdzie co powinno się znaleźć:

    • Architektura, struktura modułów, odpowiedzialność za usługi, konwencje oraz odniesienia do poleceń powinny być umieszczone w CLAUDE.md.
    • Bezwzględne zakazy, wzorce, które nigdy nie mogą zostać wygenerowane, oraz aspekty bezpieczeństwa lub wydajności należą do katalogu rules/.

    Pamiętaj, że reguły to instrukcje ładowane do kontekstu modelu, a nie narzędzie do analizy statycznej. Sprawiają one, że naruszenia stają się znacznie mniej prawdopodobne, ale w przypadku krytycznych zasad nadal warto je wspierać regułami lintingu, testami lub kontrolami CI.

    Reguła: brak zapytań typu N+1

    Zapytania typu N+1 są łatwe do napisania, ale też łatwo o nich zapomnieć podczas przeglądania, ponieważ każda pojedyncza linia wygląda niewinnie. Zasada zawarta w pliku .claude/rules/database-queries.md działa najlepiej, gdy pokazuje zarówno wzorzec zabroniony, jak i jego zamiennik. Wersja zabroniona generuje jedno zapytanie na każdą rezerwację:

    // Wrong — N queries
    for (const b of bookings) {
      b.user = await this.userRepo.findOne(b.userId);
    }
    

    Zamiennik ładuje wszystkich użytkowników za pomocą jednego zapytania przy użyciu operatora In z TypeORM, a następnie przechowuje ich w pamięci za pomocą struktury Map opartej na identyfikatorze id:

    // Correct — one query
    const users = await this.userRepo.find({ where: { id: In(userIds) } });
    const userMap = new Map(users.map((u) => [u.id, u]));
    bookings.forEach((b) => {
      b.user = userMap.get(b.userId);
    });
    

    Zaletą jest to, że zapytanie masowe jest tworzone w momencie generowania, dzięki czemu błąd wydajnościowy nigdy nie trafia do pull requesta. Pamiętaj, aby najpierw zebrać wartości userIds z rezerwacji i rozważyć ich usunięcie w przypadku, gdy wiele rezerwacji dotyczy tego samego użytkownika.

    Zasada: uprawnienia, nigdy nazwy ról

    Druga zasada, zawarta w pliku .claude/rules/permissions.md, zabrania takim ad hoc sprawdzaniom ról:

    // Wrong
    if (user.roles.includes('admin')) { ... }
    

    Zamiast tego punkty końcowe deklarują wymagane uprawnienia i pozwalają mechanizmom kontrolnym je egzekwować:

    // Correct
    @Permissions([PermissionCodes.MANAGE_BOOKINGS])
    @UseGuards(JwtAuthGuard, PermissionsGuard)
    

    Uzasadnienie powinno znajdować się również w pliku z regułami, ponieważ pomaga modelowi generalizować: role są łączone, dzielone i przemieniane w miarę rozwoju produktu, a każda kontrola oparta na nazwie role milcząco przestaje działać w takich sytuacjach. Kody uprawnień to stabilne stałe, które przetrwają takie reorganizacje. Reguła powinna również wskazać, gdzie one się znajdują – w tym przypadku w src/common/constants/permissions.constant.ts.

    Reguła: aliasy ścieżek zamiast importów względnych

    Trzecia reguła, .claude/rules/imports.md, standaryzuje importy:

    // Wrong
    import { UserService } from "../../services/user.service";
    // Correct
    import { UserService } from "src/modules/user/services/user.service";
    

    Alias działa, ponieważ plik tsconfig.json ustawia baseUrl na "./", a funkcja moduleNameMapper w Jest mapuje ten sam prefiks. Ważne jest wspomnienie o obu elementach: gdyby asystent znał tylko aspekty związane z TypeScriptem, mógłby uznać, że alias jest niebezpieczny w testach.

    Kolejne zasady, które warto opisać

    To samo podejście „zabraniać i pokazywać alternatywę” nadaje się do rozwiązania wielu powtarzających się problemów w NestJS. Przykłady dla własnych plików:

    • Rozwiązywanie błędów: rzucaj wyjątki typu NestJS utworzone na podstawie stałych ErrorMessages lub ResponseCodeKeys; nigdy nie wpisywaj treści błędów bezpośrednio w kodzie ani nie zwracaj surowych obiektów błędów.
    • Logowanie: używaj strukturyzowanego narzędzia logMessage() z odpowiednim poziomem; console.log jest niedozwolone.
  • Transakcje: operacje, które są wykonywane więcej niż raz, muszą korzystać z jednej transakcji EntityManager, dzięki czemu awaria nie może spowodować częściowego zapisania danych.
  • Walidacja DTO: każde DTO w żądaniu wymaga dekoratorów z biblioteki class-validator. Ponieważ ValidationPipe działa w trybie białej listy, właściwości bez dekoratorów są cicho pomijane, co wygląda jak utrata danych, a nie błąd.
  • Równoważność entity i migracji: każda zmiana w entity wymaga odpowiadającej jej migracji Flyway, a wartość synchronize pozostaje na poziomie false.
  • Coverage Swagger: każdy endpoint jest opatrzony anotacją @ApiSpec oraz typowanym DTO odpowiedzi, dzięki czemu generowana dokumentacja API pozostaje dokładna.
  • Krok 4: przekształcanie powtarzających się procesów w umiejętności

    Umiejętności to specyficzne dla projektu polecenia typu slash, które łączą w sobie procedury, szablony oraz materiały referencyjne. Są one odpowiednim narzędziem do wykonywania często powtarzanych zadań wymagających wielu plików: dodawania punktu końcowego, pisania migracji, tworzenia szkieletu modułu czy przeglądania pull requestów. W odróżnieniu od CLAUDE.md zawartość umiejętności jest ładowana tylko wtedy, gdy jest ona używana, dzięki czemu szczegółowe szablony nie obciążają pamięci w każdej sesji.

    Anatomia katalogu umiejętności

    Umiejętność służąca do tworzenia punktów końcowych może być zorganizowana w ten sposób: proces pracy opisany jest w pliku SKILL.md, szablony znajdują się w katalogu assets/, a materiały referencyjne – w katalogu references/:

    .claude/skills/api-development/
    ├── SKILL.md                    # Workflow, trigger, steps
    ├── assets/
    │   ├── controller-template.md
    │   ├── service-template.md
    │   ├── repository-template.md
    │   └── dto-template.md
    └── references/
        ├── nestjs-conventions.md
        └── swagger-documentation.md
    

    Proces pracy opisany w SKILL.md

    Plik zaczyna się od prostego tytułu:

    API Development Skill
    

    Następnie w jego treści określono, kiedy należy wywołać daną umiejętność, oraz przedstawiono kroki w ustalonej kolejności:

    ## TriggerInvoke with `/api-development` when building or modifying a NestJS endpoint.## Workflow1. **Determine service & module** — check CLAUDE.md routing; ask if ambiguous.
    2. **Read what exists** — entities, repositories, DTOs already in the module.
    3. **Generate the DTO first** — it defines the contract. class-validator
       decorators; extend `PaginationRequestDTO` for lists.
    4. **Generate the repository** — inject `DataSource`; accept an optional
       `EntityManager` for transactions; queries only, no logic.
    5. **Generate the service** — inject the repository interface token; use
       `ErrorMessages` constants; throw `NotFoundException` etc.
    6. **Generate the controller** — `@ApiSpec` + `@ApiOkResponse`; return
       `{ data, meta }`; no business logic.
    7. **Wire the module** — add to `providers`, export by interface token.
    

    Kolejność ta jest celowa. Sprawdzenie tabeli routingu najpierw zapobiega przekierowaniu umiejętności do niewłaściwej usługi. Czytanie istniejących entytetów, repositoriów i DTO-ów unika ich duplikowania. Generowanie DTO przed czymkolwiek innym ustala kontrakt, a każda kolejna warstwa jest budowana na jego podstawie: repositoryj akceptuje opcjonalny EntityManager, aby móc łączyć transakcje, usługa jest iniektowana za pomocą tokenu interfejsu i rzuca standardowe wyjątki z użyciem stałych ErrorMessages, natomiast kontroler jedynie dokumentuje, deleguje i zwraca { data, meta }. Ostatni krok łączy dostawców z modułem i eksportuje je za pomocą tokena.

    Jedna praktyczna uwaga: aktualne wersje Claude Code odkrywają umiejętności za pomocą danych YAML znajdujących się na początku pliku SKILL.md (pole name oraz description, które określają, kiedy należy je użyć). Powyższy przykład ich pomija, więc sprawdź aktualną dokumentację pod kątem dokładnego formatu przy tworzeniu własnych plików.

    Środki powinny być prawdziwym kodem

    Środki w katalogu assets/ są przydatne tylko wtedy, gdy dają się skompilować. Pseudokod zachęca model do uzupełniania braków własnymi standardowymi wartościami. Szablon kontrolera zaczyna się od deklaracji klasy oraz iniekcji tokenów:

    @ApiTags("bookings")
    @Controller("bookings")
    export class BookingController {
      constructor(
        @Inject(SERVICE_INTERFACE.BOOKING_SERVICE)
        private readonly bookingService: IBookingService,
      ) {}
    

    Dalej znajduje się kompletny punkt końcowy, który jednocześnie pokazuje kolejność zabezpieczeń, dekorator uprawnień, dekoratory Swagger oraz strukturę zwracanych danych:

      @Post()
      @UseGuards(JwtAuthGuard, PermissionsGuard)
      @Permissions([PermissionCodes.CREATE_BOOKING])
      @ApiSpec({ summary: "Create a new booking" })
      @ApiCreatedResponse({ type: CreateBookingSuccessDto })
      async createBooking(
        @Body() dto: CreateBookingDto,
        @Req() req: UserRequest,
      ): Promise<{ data: BookingEntity }> {
        const booking = await this.bookingService.createBooking(dto, req.user.id);
        return { data: booking };
      }
    }
    

    Zwróć uwagę na to, czego uczy ten pojedynczy przykład: JwtAuthGuard jest wykonywany przed PermissionsGuard, identyfikator użytkownika pochodzi z typowanej struktury UserRequest, kontroler nie zawiera logiki biznesowej, a funkcja zwraca { data }, aby mógł ją otoczyć TransformInterceptor. Dzięki takiemu podejściu opisany scenariusz pozwala na stworzenie kompletnego punktu końcowego (kontroler, usługa, repozytorium oraz DTO-y połączone z modułem) w mniej niż trzy minuty, zamiast około dwudziestu, jak to miało miejsce przy pierwszej próbie w zespole. Twoje wyniki będą zależeć od rozmiaru szablonów oraz od tego, ile jeszcze przeglądów wykonasz.

    Inne umiejętności do rozważenia

    Każdy powtarzający się proces pracy, który obejmuje specyficzne dla zespołu konwencje, jest dobrym kandydatem:

    • Generator migracji: tworzy migrację Flyway w odpowiedniej folderze schematu, korzystając z Twojego schematu numeracji i nazywania wraz z odpowiadającą zmianą w entytecie.
    • Budowniczy modułu: generuje plik modułu, trzy warstwy oraz tokeny DI, już połączone ze sobą.
    • Przegląd żądania pull: sprawdza różnice w kodzie pod kątem zapytań typu N+1, wykorzystania uprawnień, naruszeń zasad warstw oraz braku testów.
    • Generator testów: tworzy pliki *.spec.ts, które przestrzegają konwencji mockowania i przygotowywania danych pomocniczych dla usług.
    • Adapter międzyusługowy: dodaje metodę adaptera IMS wraz z interfejsem odpowiedzi i pomocnikiem do transformacji.
    • Dokumentacja przypadków użycia: przekształca wymagania w historie użytkownika z kryteriami akceptacji typu Given/When/Then.

    Krok 5: konfiguracja uprawnień

    .claude/settings.json określa, jakie polecenia wiersza poleceń asystent może wykonać bez uprzedniego pytania. Rozsądna podstawa pozwala z góry na linting, testy i budowanie, a wyraźnie blokuje polecenia, które niszczą prace lub je publikują:

    {
      "permissions": {
        "allow": [
          "Bash(npm run lint:*)",
          "Bash(npm run test:*)",
          "Bash(npm run build)",
          "Bash(npx jest *)"
        ],
        "deny": ["Bash(rm -rf *)", "Bash(git push *)", "Bash(git reset --hard *)"]
      }
    }
    

    Ustawienia dzielą się na dwa poziomy:

    • .claude/settings.json dotyczy całego zespołu i jest komitowany.
    • .claude/settings.local.json zawiera osobiste modyfikacje, dotyczy tylko ciebie i powinien być oznaczony jako gitignored.

    Lista blokad to zabezpieczenie awaryjne, a nie sandbox. Wzory odpowiadają ciągom poleceń, więc należy je traktować jako środek zapobiegający wypadkom, a nie jako granicę bezpieczeństwa.

    Praktyki utrzymujące zdrowy stan konfiguracji

    Traktuj .claude/ jak kod produkcyjny: rozpowszechnia twoje konwencje wśród wszystkich inżynierów i na każdej sesji, więc zasługuje na przegląd, historię i regularną oczyszczanie.

    • Zachowuj krótkość pliku CLAUDE.md, maksymalnie około 300 wierszy. Wszystko, co się w nim znajduje, konkuruje o przestrzeń roboczą z kodem, który asystent musi przeczytać. Jeśli jakaś informacja można wywnioskować z kodu, lepiej jej pominąć.
    • Sformułuj zasady jako zakazy. W praktyce „nigdy nie wykonywaj zapytań do bazy danych wewnątrz pętli” znacznie lepiej sprawdza się pod presją niż „wolimy zapytania masowe”.
    • Zachowuj szablony w kompetencjach. Powinny się ładować tylko wtedy, gdy uruchamiana jest dana kompetencja, a nie przy każdej sesji.
    • Zachowuj wersję .claude/ w git. Nowe zasady trafiają poprzez pull requests, a te przestarzałe są usuwane.
    • Używaj tabel decyzyjnych tam, gdzie istnieją dwie możliwe opcje, np. przy umieszczaniu DTO, lokalizacji entity lub granicach usług. W plikach Markdown przeznaczonych dla asystenta tabela jest bardziej zrozumiała niż akapity.

    .claude/
    ├── CLAUDE.md                  # Root guide (<300 lines)
    ├── settings.json              # Team permissions (committed)
    ├── rules/
    │   ├── database-queries.md    # No N+1
    │   ├── permissions.md         # PermissionCodes, no role checks
    │   └── imports.md             # src/ alias only
    └── skills/
        └── api-development/
            ├── SKILL.md
            ├── assets/            # controller/service/repository/dto templates
            └── references/        # conventions, swagger
    

    Błędy, które po cichu pogarszają jakość wyników

    Każdy z nich generuje kod, który wygląda poprawnie, ale narusza ustalone zasady, co sprawia, że są one kosztowne.

    1. Zapisywanie pliku CLAUDE.md jako tutorialu do NestJS. Model już zna tę ramę programistyczną. Dokumentuj tylko to, co jest specyficzne dla Ciebie: nazwy modułów, stałe błędów, strukturę odpowiedzi, system uprawnień.
    2. Pomijanie negatywnych zasad. Najdroższe błędy to te, które wyglądają poprawnie; podanie asystentowi informacji o tym, czego nie robić, eliminuje całe kategorie takich błędów.
    3. Umieszczanie wszystkiego w pliku korzeniowym. Plik korzeniowy jest ładowany przy każdej sesji, więc specyficzna dla usług głębokość struktury w tym pliku zanieczyszcza kontekst podczas pracy w innych miejscach.
  • Pomijanie tabeli routingu. Błędne umieszczenie usług wynika z niejednoznaczności, a nie z braku wiedzy, a tabela sprawia, że decyzja jest deterministyczna.
  • Niedokumentowanie wzorca adaptera. W usługach połączonych przez HTTP asystent w przeciwnym razie będzie próbował wykonać importy międzyusługowe lub zapytania do bazy danych innej usługi.
  • Nieuwzględnienie synchronize: false. Model założy, że to TypeORM zarządza schematem. Gdy odpowiedzialny jest Flyway, zmiana entity bez migracji kończy się bez żadnych komunikatów błędu, dlatego należy to jasno określić.
  • Przebieg: jedna prośba, przed i po

    Załóżmy, że menedżer produktu prosi o dostęp do rezerwacji, aby pokazać status, który klienci mogliby zobaczyć na swoim pakiecie.

    W niekonfigurowanym repozytorium asystent zakłada, że pakiety są zdefiniowane w operator-service, i edytuje tam entytę pakietu, co dotyczy niewłaściwego usługi. Zapisuje ściśle określoną wartość pola statusu i zabezpiecza tę zmianę za pomocą if (user.roles.includes('admin')).

    Dzięki konfiguracji z tego przewodnika żądanie jest realizowane krok po kroku:

    1. Tabela routingu klasyfikuje status widoczny dla konsumenta jako element interakcji z nim, więc praca powinna odbywać się w api-service.
    2. Zasada międzyusługowa przypomina o tym, że dane pakietu operatora są czytane za pośrednictwem adaptera IMS, więc pole to jest dodawane do funkcji transformacji adaptera zamiast być pobierane z innej bazy danych.
    3. Zapewnienie działania /api-development generuje cztery warstwy kolejno, począwszy od DTO i kończąc na kontrolerze.
  • Zasada uprawnień generuje @Permissions([PermissionCodes.VIEW_BOOKING_STATUS]) zamiast sprawdzania ról.
  • Zasada obsługi błędów tworzy obiekt NotFoundException na podstawie stałej ErrorMessages.
  • Jedno niejasne zdanie prowadzi do zmian zgodnych z konwencjami we czterech plikach, bez potrzeby zadawania dodatkowych pytań wyjaśniających.

    Co zmienia się na co dzień

    Konfiguracja zajmuje kilka godzin raz. Zmiany, które ma zapewnić:

    • Zmiana punktu skupienia przy przeglądaniu. Zamiast sprawdzania każdego utworzonego pliku pod kątem sztywno zdefiniowanych ciągów znaków, błędnych importów i sprawdzania ról, recenzenci skupiają uwagę na logice.
    • Szybsze końce punktów dostępu. Łączenie czterech warstw ręcznie zajmowało około dwudziestu minut w opisanym setupie; dzięki tej umiejętności czas ten spada poniżej trzech minut.
  • Miejsce umieszczenia pliku jest poprawne od razu. Tabela routingu kieruje pliki do odpowiedniego usługi, zamiast dopiero później odkrywać błąd podczas sprawdzania.
  • Powszechne błędy są unikane na etapie tworzenia. Zapytania typu N+1 oraz niepotrzebne wywołania console.log są zapobiegane już podczas generowania, zamiast być znajdowane w pull requestach.
  • Proces wprowadzania do pracy ulega udoskonaleniu. Nowi inżynierowie nie muszą już zgadywać, gdzie znajdują się granice modułów, ponieważ zasady są spisane i stosowane już przy pierwszej funkcji tworzonej przy pomocy AI.
  • Część korzyści pojawia się dopiero po kilku miesiącach. Kod generowany w dużych odstępach czasu wygląda spójnie, ponieważ pochodzi z tych samych szablonów i reguł. Recenzenci przestają pisać te same komentarze. Tabele routingu i reguły pełnią rolę dokumentacji, która jest faktycznie wykorzystywana. A zwięzły plik CLAUDE.md w połączeniu z umiejętnościami dostępnymi na żądanie oznacza, że zasoby przeznaczane są na czytanie kodu, a nie na ponowne odkrywanie konwencji.

    Częste pytania

    Gdzie umieszcza się pliki w monorepo? Użytkownik powinien umieścić wspólny plik CLAUDE.md w katalogu .claude/ u korzenia repozytorium git, aby zajmować się kwestiami wspólnymi dla wszystkich elementów, a osobny plik CLAUDE.md wewnątrz każdej usługi – dla jej specyficznych potrzeb. Plik z korzenia jest zawsze ładowany; plik usługi zostaje załadowany, gdy pracuje się w danej usłudze.

    Jak CLAUDE.md różni się od reguł? CLAUDE.md wyjaśnia, jak działa system; reguły określają, co nie może się pojawić, np. zapytania w pętlach lub sprawdzanie nazw ról.

    Jak zachowują się umiejętności? Umiejętność taka jak /api-development opiera się na pliku SKILL.md wraz z szablonami i odnośnikami. Jej wywołanie sprawia, że asystent postępuje zgodnie z ustaloną, wieloetapową procedurą, zamiast improwizować.

    Czy .claude/ powinno być zapisywane w git? Tak: CLAUDE.md, rules/, skills/ oraz settings.json powinny znajdować się w git, aby zespół mógł korzystać z jednej konfiguracji. Osobiste modyfikacje trafiają do pliku settings.local.json w katalogu gitignored.

    Główne wnioski

    • W monorepo NestJS poprawność zależy od konwencji, których nie pokazuje żaden pojedynczy plik, więc asystent bez kontekstu będzie zgadywał.
    • Rozdziel te informacje według typu: CLAUDE.md opisuje, jak działają poszczególne elementy, zasady określają to, co jest zabronione, umiejętności dotyczą procedur powtarzalnych, a ustawienia to, co może działać bez nadzoru.
    • Tabela decyzji dotyczących odpowiedzialności za usługi to najważniejszy element; napisz ją pierwszą.
    • Dokumentuj celowe usunięcia oraz wybory inne niż domyślne, takie jak synchronize: false, ponieważ to właśnie model w przeciwnym razie próbowałby „naprawić”.
    • Zasady silnie kierują procesem generowania, ale nie są narzędziem do egzekwowania zasad; używaj narzędzi do sprawdzania kodu, testów oraz procesów CI dla wartości niezmiennej, których nie możesz sobie pozwolić na naruszenie.
    • To inwestycja jednorazowa trwająca kilka godzin, która się zwraca przy każdej nowej funkcji i przy każdym nowym pracowniku, pod warunkiem że pliki są sprawdzane i oczyszczane tak samo jak kod.

    Powiązane materiały

  • Gdzie mieści się Turborepo w monorepo NestJS i kiedy należy go pominąć — Jak Turborepo, przestrzenie robocze pnpm oraz NestJS dzielą zadania w monorepo typu TypeScript: wspólne pakiety, grafy zadań, cacheowanie, ryzyko powiązań oraz sytuacje, gdy należy go pominąć.
  • Praktyczny przewodnik po tworzeniu skutecznego pliku CLAUDE.md — Naucz się 21 konkretnych, sprawdzalnych zasad dotyczących optymalizacji rozdętego pliku CLAUDE.md, aby Claude Code pozostawał niezawodny, przewidywalny i godny zaufania podczas długich sesji.
  • Od zasad prozy do mechanicznych bram: wzmacnianie zespołu agentów Claude Code — Jak plugin Claude Code z wieloma agentami zastąpił ignorowane instrukcje dotyczące persony skryptami, hookami oraz haszowanymi dowodami, wersja po wersji, oraz co można skopiować.
  • Where Claude Code Instructions Belong: CLAUDE.md, Path Rules or Hooks — Learn why Claude Code treats CLAUDE.md as context, how to trim it, scope rules by path, move must-run steps into hooks, and verify what actually loaded.
  • Tokeny iniekcji w NestJS: Dlaczego useClass może potajemnie kopiować instancje singleton — Zrozum typy, tokeny i dostawców w NestJS DI, dlaczego interfejsy nie mogą być iniektowane oraz jak useExisting zapobiega utworzeniu dwóch instancji tego samego usługi z pamięcią stanu.
  • Laravel czy NestJS? Porównanie architektury, szybkości i dopasowania do zespołu — Praktyczne porównanie Laravel i NestJS obejmujące architekturę, ORM-y, domyślne zasady bezpieczeństwa, szybkość działania i dostarczania aplikacji, krzywą uczenia się oraz sytuacje, w których każdy z nich jest odpowiedni.