# NestJS та GraphQL у 2026: Схеми, Резолвери та Питання на Співбесіді > Повний посібник з інтеграції NestJS GraphQL: підходи schema-first та code-first, резолвери, DataLoader та питання на технічних співбесідах 2026 року. - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Tags: nestjs, graphql, node.js, typescript, api - Reading time: 5 min --- Інтеграція NestJS з GraphQL пропонує структурований підхід до створення типобезпечних API з використанням декораторів TypeScript та мови запитів GraphQL. Цей посібник охоплює підходи schema-first та code-first, патерни резолверів, а також питання, які задають менеджери з найму на співбесідах у 2026 році. > **Code-First vs Schema-First** > > NestJS 11 за замовчуванням використовує code-first GraphQL, генеруючи схему з класів TypeScript. Підхід schema-first залишається доступним для команд з існуючими файлами `.graphql` або робочими процесами на основі SDL. ## Налаштування NestJS GraphQL з Apollo Server NestJS інтегрується з [Apollo Server](https://www.apollographql.com/docs/apollo-server/) через пакет `@nestjs/graphql`. Налаштування відрізняється залежно від обраного підходу — code-first генерує SDL з декораторів, тоді як schema-first парсить файли `.graphql` безпосередньо. ```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: генерує схему sortSchema: true, // Алфавітне сортування для читабельності playground: process.env.NODE_ENV !== 'production', // Вимкнути в продакшені introspection: process.env.NODE_ENV !== 'production', }), ], }) export class AppModule {} ``` Опція `autoSchemaFile` вмикає режим code-first. Встановлення значення `true` генерує схему в пам'яті без запису на диск — корисно для serverless-розгортань, де доступ до файлової системи може бути обмежений. ## Визначення Типів GraphQL з Декораторами Code-First Підхід code-first визначає типи GraphQL за допомогою класів TypeScript, декорованих `@ObjectType()`. Кожне поле використовує `@Field()` для вказівки типу GraphQL та можливості null. ```typescript // user.entity.ts import { ObjectType, Field, ID, Int } from '@nestjs/graphql'; @ObjectType({ description: 'Користувач додатку' }) // Опис з'являється в документації схеми export class User { @Field(() => ID) // Мапиться на скаляр GraphQL ID id: string; @Field() email: string; @Field({ nullable: true }) // Опціональне поле в GraphQL displayName?: string; @Field(() => Int, { defaultValue: 0 }) postCount: number; @Field(() => [Post], { nullable: 'itemsAndList' }) // Як список, так і елементи можуть бути null posts?: Post[]; // Поля без @Field() виключаються зі схеми GraphQL passwordHash: string; } ``` Опція `nullable` приймає три значення: `true` (поле опціональне), `'items'` (елементи списку можуть бути null) та `'itemsAndList'` (як список, так і елементи можуть бути null). Ця деталізація точно відповідає [семантиці null у GraphQL](https://graphql.org/learn/schema/#lists-and-non-null). ## Створення Резолверів для Запитів та Мутацій Резолвери обробляють вхідні операції GraphQL. NestJS використовує `@Resolver()` для позначення класу як резолвера, з декораторами методів, що вказують типи операцій. ```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) // Прив'язує резолвер до типу User для розв'язання полів export class UsersResolver { constructor(private readonly usersService: UsersService) {} @Query(() => [User], { name: 'users' }) // Явна назва запиту 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); } } ``` Декоратор `@Resolver(() => User)` встановлює контекст для декораторів `@ResolveField()`, уможливлюючи розв'язання на рівні полів для обчислюваних або пов'язаних даних. ## Input-Типи та Валідація з class-validator Input-типи GraphQL визначають payload мутацій. Поєднання `@InputType()` з декораторами [class-validator](https://github.com/typestack/class-validator) дозволяє валідацію на рівні схеми та під час виконання. ```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: 'Невірний формат email' }) email: string; @Field() @MinLength(8, { message: 'Пароль має містити щонайменше 8 символів' }) @Matches(/[A-Z]/, { message: 'Пароль має містити велику літеру' }) password: string; @Field({ nullable: true }) @IsOptional() @MinLength(2) displayName?: string; } ``` Увімкніть валідацію глобально, додавши `ValidationPipe` у файл `main.ts`. Помилки GraphQL включають повідомлення валідації в полі `extensions`, зберігаючи сумісність з клієнтами. ## Розв'язання Пов'язаних Даних з @ResolveField та DataLoader Резолвери полів обробляють зв'язки між типами. Без оптимізації, отримання списку користувачів з їхніми постами викликає N+1 запитів — один для користувачів, потім по одному для постів кожного користувача. ```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 групує запити: один запит для всіх ID користувачів 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 групує та кешує запити в межах однієї операції GraphQL. У NestJS DataLoader має бути обмежений до області запиту за допомогою `@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 }) // Новий екземпляр для кожного запиту export class PostsLoader { constructor(private readonly postsService: PostsService) {} public readonly batchByUserId = new DataLoader( async (userIds: readonly string[]) => { // Один запит: SELECT * FROM posts WHERE user_id IN (...) const posts = await this.postsService.findByUserIds([...userIds]); // Мапування результатів до вхідного порядку 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 скорочує N+1 запитів до одного пакетного запиту — критично важливо для API GraphQL, де клієнти контролюють глибину запитів. Більше інформації про архітектурні патерни NestJS можна знайти в модулі [питання співбесіди про модулі та Dependency Injection в NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di). ## Підписки для Даних у Реальному Часі Підписки GraphQL передають дані клієнтам через з'єднання WebSocket. NestJS використовує бібліотеку `graphql-ws`, яка реалізує [протокол GraphQL over WebSocket](https://github.com/enisdenjo/graphql-ws). ```typescript // app.module.ts - Увімкнення підписок GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, subscriptions: { 'graphql-ws': true, // Сучасний протокол 'subscriptions-transport-ws': false, // Застарілий 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(); // Використовуйте Redis PubSub для багатоекземплярних розгортань @Resolver(() => Post) export class PostsResolver { @Subscription(() => Post, { filter: (payload, variables) => payload.postCreated.userId === variables.userId, // Фільтрація на стороні клієнта }) 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 }); // Викликати підписку return post; } } ``` Для продакшн-розгортань на кількох екземплярах in-memory `PubSub` слід замінити на `graphql-redis-subscriptions` для трансляції подій по всьому кластеру. ## Автентифікація та Авторизація в GraphQL Гарди NestJS безперешкодно працюють з резолверами GraphQL. Контекст виконання відрізняється від REST — використовуйте `GqlExecutionContext` для отримання запиту. ```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; // Отримати запит з контексту GraphQL } } ``` Гарди застосовуються на рівні резолвера або методу. Авторизація на рівні полів використовує `@ResolveField()` з умовною логікою на основі ролей користувача. ```typescript // users.resolver.ts @UseGuards(GqlAuthGuard) @Resolver(() => User) export class UsersResolver { @Query(() => User) me(@CurrentUser() user: User): User { return user; // Повернути автентифікованого користувача } @ResolveField(() => String, { nullable: true }) email(@Parent() user: User, @CurrentUser() currentUser: User): string | null { // Повертати email лише при перегляді власного профілю або якщо admin if (user.id === currentUser.id || currentUser.role === 'ADMIN') { return user.email; } return null; } } ``` Для складної авторизації варто розглянути [GraphQL Shield](https://github.com/dimatill/graphql-shield) або вбудовану інтеграцію CASL в NestJS. Стаття [Гарди та Інтерцептори в NestJS](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) детально розглядає ці патерни. ## Поширені Питання на Співбесіді NestJS GraphQL Технічні співбесіди на позиції NestJS часто включають питання, специфічні для GraphQL. Ось патерни, які оцінюють менеджери з найму. **П: Як NestJS справляється з проблемою N+1 у GraphQL?** DataLoader групує виклики резолверів полів у межах одного запиту. Коли кілька батьківських об'єктів запитують одне поле, DataLoader збирає всі ключі, виконує один пакетний запит і розподіляє результати. Loader має бути обмежений до області запиту, щоб запобігти проблемам з кешуванням між запитами. **П: Яка різниця між code-first та schema-first у NestJS GraphQL?** Code-first генерує схему GraphQL з декораторів TypeScript під час виконання, автоматично синхронізуючи типи та схему. Schema-first парсить SDL-файли `.graphql`, вимагаючи ручних визначень типів. Code-first підходить командам, які нативно працюють з TypeScript; schema-first краще працює, коли схема є контрактом між frontend та backend командами. **П: Як реалізувати дозволи на рівні полів?** Існує три підходи: (1) `@ResolveField()` з умовними поверненнями на основі контексту користувача, (2) Власні декоратори, що перевіряють дозволи перед розв'язанням поля, (3) Директиви схеми на кшталт `@auth(requires: ADMIN)`, що обробляються трансформером директив. Перший підхід пропонує найбільшу гнучкість; директиви забезпечують найчистішу документацію схеми. **П: Поясніть контекст GraphQL у NestJS.** Об'єкт контексту проходить через усі резолвери в межах запиту. NestJS за замовчуванням заповнює його HTTP-запитом. Власний контекст налаштовується в `GraphQLModule.forRoot()` через опцію `context` — корисно для додавання екземплярів DataLoader, автентифікованого користувача або з'єднань з базою даних. **П: Як підписки масштабуються на кількох екземплярах сервера?** In-memory PubSub працює лише для одноекземплярних розгортань. Багатоекземплярні архітектури вимагають зовнішнього брокера — Redis PubSub є стандартом. Кожен екземпляр сервера підписується на канали Redis; коли будь-який екземпляр публікує подію, всі екземпляри її отримують і передають підключеним WebSocket-клієнтам. Для додаткової підготовки до співбесід з NestJS дивіться модуль [Middleware та Інтерцептори](/technologies/node-nestjs/interview-questions/middleware-interceptors), що охоплює патерни життєвого циклу запиту. ## Висновок - NestJS GraphQL підтримує обидва підходи: code-first та schema-first — code-first спрощує проекти TypeScript, schema-first підходить для робочих процесів на основі SDL - DataLoader усуває N+1 запити шляхом групування запитів резолверів полів у межах однієї операції - Екземпляри DataLoader, обмежені до області запиту, запобігають забрудненню кешу між одночасними запитами - `GqlExecutionContext` з'єднує Гарди NestJS з контекстом резолверів GraphQL - Продакшн-підписки вимагають Redis PubSub для розподілу подій між кількома екземплярами - Авторизація на рівні полів поєднує `@ResolveField()` з перевірками контексту користувача --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial