# NestJS i GraphQL w 2026: Schematy, Resolvery i Pytania Rekrutacyjne > Kompletny poradnik NestJS GraphQL omawiający podejścia schema-first i code-first, resolvery, DataLoader oraz pytania rekrutacyjne w 2026 roku. - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Tags: nestjs, graphql, node.js, typescript, api - Reading time: 5 min --- Integracja NestJS z GraphQL oferuje strukturalne podejście do tworzenia bezpiecznych typowo API, wykorzystujących dekoratory TypeScript i język zapytań GraphQL. Ten poradnik omawia podejścia schema-first i code-first, wzorce resolverów oraz pytania rekrutacyjne zadawane przez menedżerów podczas rozmów kwalifikacyjnych w 2026 roku. > **Code-First vs Schema-First** > > NestJS 11 domyślnie używa GraphQL code-first, generując schemat z klas TypeScript. Podejście schema-first pozostaje dostępne dla zespołów posiadających istniejące pliki `.graphql` lub pracujących z SDL. ## Konfiguracja NestJS GraphQL z Apollo Server NestJS integruje się z [Apollo Server](https://www.apollographql.com/docs/apollo-server/) poprzez pakiet `@nestjs/graphql`. Konfiguracja różni się w zależności od wybranego podejścia — code-first generuje SDL z dekoratorów, podczas gdy schema-first parsuje pliki `.graphql` bezpośrednio. ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // Code-first: generuje schemat sortSchema: true, // Alfabetyczne sortowanie dla czytelności playground: process.env.NODE_ENV !== 'production', // Wyłącz w produkcji introspection: process.env.NODE_ENV !== 'production', }), ], }) export class AppModule {} ``` Opcja `autoSchemaFile` włącza tryb code-first. Ustawienie wartości na `true` generuje schemat w pamięci bez zapisu na dysk — przydatne dla wdrożeń serverless, gdzie dostęp do systemu plików może być ograniczony. ## Definiowanie Typów GraphQL z Dekoratorami Code-First Podejście code-first definiuje typy GraphQL przy użyciu klas TypeScript ozdobionych dekoratorem `@ObjectType()`. Każde pole używa `@Field()` do określenia typu GraphQL i dopuszczalności wartości null. ```typescript // user.entity.ts import { ObjectType, Field, ID, Int } from '@nestjs/graphql'; @ObjectType({ description: 'Użytkownik aplikacji' }) // Opis pojawia się w dokumentacji schematu export class User { @Field(() => ID) // Mapuje do skalaru GraphQL ID id: string; @Field() email: string; @Field({ nullable: true }) // Opcjonalne pole w GraphQL displayName?: string; @Field(() => Int, { defaultValue: 0 }) postCount: number; @Field(() => [Post], { nullable: 'itemsAndList' }) // Zarówno lista, jak i elementy mogą być null posts?: Post[]; // Pola bez @Field() są wykluczone ze schematu GraphQL passwordHash: string; } ``` Opcja `nullable` przyjmuje trzy wartości: `true` (pole opcjonalne), `'items'` (elementy listy mogą być null) oraz `'itemsAndList'` (zarówno lista, jak i elementy mogą być null). Ta granularność dokładnie odpowiada [semantyce null w GraphQL](https://graphql.org/learn/schema/#lists-and-non-null). ## Tworzenie Resolverów dla Zapytań i Mutacji Resolvery obsługują przychodzące operacje GraphQL. NestJS używa `@Resolver()` do oznaczenia klasy jako resolvera, z dekoratorami metod określającymi typy operacji. ```typescript // users.resolver.ts import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql'; import { User } from './user.entity'; import { UsersService } from './users.service'; import { CreateUserInput } from './dto/create-user.input'; @Resolver(() => User) // Wiąże resolver z typem User do rozwiązywania pól export class UsersResolver { constructor(private readonly usersService: UsersService) {} @Query(() => [User], { name: 'users' }) // Jawna nazwa zapytania findAll(): Promise { return this.usersService.findAll(); } @Query(() => User, { nullable: true }) user(@Args('id', { type: () => ID }) id: string): Promise { return this.usersService.findOne(id); } @Mutation(() => User) createUser(@Args('input') input: CreateUserInput): Promise { return this.usersService.create(input); } } ``` Dekorator `@Resolver(() => User)` ustala kontekst dla dekoratorów `@ResolveField()`, umożliwiając rozwiązywanie na poziomie pól dla danych obliczanych lub powiązanych. ## Typy Input i Walidacja z class-validator Typy input GraphQL definiują payload mutacji. Połączenie `@InputType()` z dekoratorami [class-validator](https://github.com/typestack/class-validator) umożliwia walidację na poziomie schematu i w czasie wykonania. ```typescript // dto/create-user.input.ts import { InputType, Field } from '@nestjs/graphql'; import { IsEmail, MinLength, IsOptional, Matches } from 'class-validator'; @InputType() export class CreateUserInput { @Field() @IsEmail({}, { message: 'Nieprawidłowy format email' }) email: string; @Field() @MinLength(8, { message: 'Hasło musi mieć co najmniej 8 znaków' }) @Matches(/[A-Z]/, { message: 'Hasło musi zawierać wielką literę' }) password: string; @Field({ nullable: true }) @IsOptional() @MinLength(2) displayName?: string; } ``` Walidację należy włączyć globalnie, dodając `ValidationPipe` w pliku `main.ts`. Błędy GraphQL zawierają komunikaty walidacji w polu `extensions`, zachowując kompatybilność z klientami. ## Rozwiązywanie Powiązanych Danych z @ResolveField i DataLoader Resolvery pól obsługują relacje między typami. Bez optymalizacji, pobieranie listy użytkowników wraz z ich postami wywołuje zapytania N+1 — jedno dla użytkowników, a następnie jedno na każdego użytkownika dla postów. ```typescript // users.resolver.ts import { Resolver, ResolveField, Parent } from '@nestjs/graphql'; import { User } from './user.entity'; import { Post } from '../posts/post.entity'; import { PostsLoader } from '../posts/posts.loader'; @Resolver(() => User) export class UsersResolver { constructor(private readonly postsLoader: PostsLoader) {} @ResolveField(() => [Post]) async posts(@Parent() user: User): Promise { // DataLoader grupuje żądania: jedno zapytanie dla wszystkich ID użytkowników return this.postsLoader.batchByUserId.load(user.id); } @ResolveField(() => Int) async postCount(@Parent() user: User): Promise { const posts = await this.postsLoader.batchByUserId.load(user.id); return posts.length; } } ``` DataLoader grupuje i cachuje żądania w ramach pojedynczej operacji GraphQL. W NestJS DataLoader powinien być ograniczony do zakresu żądania przy użyciu `@Injectable({ scope: Scope.REQUEST })`. ```typescript // posts/posts.loader.ts import { Injectable, Scope } from '@nestjs/common'; import * as DataLoader from 'dataloader'; import { PostsService } from './posts.service'; import { Post } from './post.entity'; @Injectable({ scope: Scope.REQUEST }) // Nowa instancja dla każdego żądania export class PostsLoader { constructor(private readonly postsService: PostsService) {} public readonly batchByUserId = new DataLoader( async (userIds: readonly string[]) => { // Pojedyncze zapytanie: SELECT * FROM posts WHERE user_id IN (...) const posts = await this.postsService.findByUserIds([...userIds]); // Mapuj wyniki z powrotem do kolejności wejściowej const postsMap = new Map(); posts.forEach(post => { const existing = postsMap.get(post.userId) || []; postsMap.set(post.userId, [...existing, post]); }); return userIds.map(id => postsMap.get(id) || []); } ); } ``` DataLoader redukuje zapytania N+1 do pojedynczego zgrupowanego zapytania — kluczowe dla API GraphQL, gdzie klienci kontrolują głębokość zapytań. Więcej informacji o wzorcach architektonicznych NestJS można znaleźć w module [pytania rekrutacyjne o moduły i Dependency Injection w NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di). ## Subskrypcje dla Danych w Czasie Rzeczywistym Subskrypcje GraphQL przesyłają dane do klientów przez połączenia WebSocket. NestJS wykorzystuje bibliotekę `graphql-ws`, która implementuje [protokół GraphQL over WebSocket](https://github.com/enisdenjo/graphql-ws). ```typescript // app.module.ts - Włączenie subskrypcji GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, subscriptions: { 'graphql-ws': true, // Nowoczesny protokół 'subscriptions-transport-ws': false, // Przestarzały protokół legacy }, }), ``` ```typescript // posts.resolver.ts import { Resolver, Subscription } from '@nestjs/graphql'; import { PubSub } from 'graphql-subscriptions'; import { Post } from './post.entity'; const pubSub = new PubSub(); // Użyj Redis PubSub dla wdrożeń wieloinstancyjnych @Resolver(() => Post) export class PostsResolver { @Subscription(() => Post, { filter: (payload, variables) => payload.postCreated.userId === variables.userId, // Filtrowanie po stronie klienta }) postCreated() { return pubSub.asyncIterableIterator('postCreated'); } @Mutation(() => Post) async createPost(@Args('input') input: CreatePostInput): Promise { const post = await this.postsService.create(input); pubSub.publish('postCreated', { postCreated: post }); // Wywołaj subskrypcję return post; } } ``` Dla wdrożeń produkcyjnych na wielu instancjach, pamięciowy `PubSub` należy zastąpić biblioteką `graphql-redis-subscriptions`, aby transmitować zdarzenia w całym klastrze. ## Uwierzytelnianie i Autoryzacja w GraphQL Guardy NestJS współpracują bezproblemowo z resolverami GraphQL. Kontekst wykonania różni się od REST — należy użyć `GqlExecutionContext` do wyodrębnienia żądania. ```typescript // guards/gql-auth.guard.ts import { Injectable, ExecutionContext } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class GqlAuthGuard extends AuthGuard('jwt') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context); return ctx.getContext().req; // Wyodrębnij żądanie z kontekstu GraphQL } } ``` Guardy stosuje się na poziomie resolvera lub metody. Autoryzacja na poziomie pól wykorzystuje `@ResolveField()` z logiką warunkową opartą na rolach użytkownika. ```typescript // users.resolver.ts @UseGuards(GqlAuthGuard) @Resolver(() => User) export class UsersResolver { @Query(() => User) me(@CurrentUser() user: User): User { return user; // Zwróć uwierzytelnionego użytkownika } @ResolveField(() => String, { nullable: true }) email(@Parent() user: User, @CurrentUser() currentUser: User): string | null { // Zwróć email tylko przy przeglądaniu własnego profilu lub jako admin if (user.id === currentUser.id || currentUser.role === 'ADMIN') { return user.email; } return null; } } ``` Dla złożonej autoryzacji warto rozważyć [GraphQL Shield](https://github.com/dimatill/graphql-shield) lub wbudowaną integrację CASL w NestJS. Artykuł [Guardy i Interceptory w NestJS](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) szczegółowo omawia te wzorce. ## Popularne Pytania Rekrutacyjne NestJS GraphQL Rozmowy techniczne na stanowiska NestJS często zawierają pytania specyficzne dla GraphQL. Oto wzorce oceniane przez menedżerów rekrutujących. **P: Jak NestJS radzi sobie z problemem N+1 w GraphQL?** DataLoader grupuje wywołania resolverów pól w ramach pojedynczego żądania. Gdy wiele obiektów nadrzędnych żąda tego samego pola, DataLoader zbiera wszystkie klucze, wykonuje jedno zgrupowane zapytanie i dystrybuuje wyniki. Loader musi być ograniczony do zakresu żądania, aby zapobiec problemom z cachowaniem między żądaniami. **P: Jaka jest różnica między code-first a schema-first w NestJS GraphQL?** Code-first generuje schemat GraphQL z dekoratorów TypeScript w czasie wykonania, automatycznie synchronizując typy i schemat. Schema-first parsuje pliki SDL `.graphql`, wymagając ręcznych definicji typów. Code-first pasuje do zespołów natywnie pracujących z TypeScript; schema-first sprawdza się lepiej, gdy schemat jest kontraktem między zespołami frontend i backend. **P: Jak zaimplementować uprawnienia na poziomie pól?** Istnieją trzy podejścia: (1) `@ResolveField()` z warunkowymi zwrotami opartymi na kontekście użytkownika, (2) Własne dekoratory sprawdzające uprawnienia przed rozwiązaniem pola, (3) Dyrektywy schematu jak `@auth(requires: ADMIN)` przetwarzane przez transformer dyrektyw. Pierwsze podejście oferuje największą elastyczność; dyrektywy zapewniają najczystszą dokumentację schematu. **P: Wyjaśnij kontekst GraphQL w NestJS.** Obiekt kontekstu przechodzi przez wszystkie resolvery w ramach żądania. NestJS domyślnie wypełnia go żądaniem HTTP. Własny kontekst konfiguruje się w `GraphQLModule.forRoot()` poprzez opcję `context` — przydatne do dodawania instancji DataLoader, uwierzytelnionego użytkownika lub połączeń z bazą danych. **P: Jak subskrypcje skalują się na wielu instancjach serwera?** Pamięciowy PubSub działa tylko dla wdrożeń jednoinstancyjnych. Architektury wieloinstancyjne wymagają zewnętrznego brokera — standardem jest Redis PubSub. Każda instancja serwera subskrybuje kanały Redis; gdy dowolna instancja publikuje zdarzenie, wszystkie instancje je odbierają i przesyłają do połączonych klientów WebSocket. Dodatkowe przygotowanie do rozmów o NestJS można znaleźć w module [Middleware i Interceptory](/technologies/node-nestjs/interview-questions/middleware-interceptors) omawiającym wzorce cyklu życia żądań. ## Podsumowanie - NestJS GraphQL wspiera oba podejścia: code-first i schema-first — code-first upraszcza projekty TypeScript, schema-first pasuje do pracy opartej na SDL - DataLoader eliminuje zapytania N+1 poprzez grupowanie żądań resolverów pól w ramach pojedynczej operacji - Instancje DataLoader ograniczone do zakresu żądania zapobiegają zanieczyszczeniu cache między równoczesnymi żądaniami - `GqlExecutionContext` łączy Guardy NestJS z kontekstem resolverów GraphQL - Produkcyjne subskrypcje wymagają Redis PubSub dla dystrybucji zdarzeń między wieloma instancjami - Autoryzacja na poziomie pól łączy `@ResolveField()` ze sprawdzaniem kontekstu użytkownika --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial