# NestJS and GraphQL in 2026: Schemas, Resolvers and Interview Questions > Master NestJS GraphQL integration with schema-first and code-first approaches, resolver patterns, and common interview questions for senior developer positions. - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Tags: nestjs, graphql, nodejs, typescript, api, tutorial - Reading time: 12 min --- NestJS GraphQL integration provides a structured approach to building type-safe APIs that leverage TypeScript decorators and GraphQL's query language. This tutorial covers both schema-first and code-first approaches, resolver patterns, and the interview questions hiring managers ask in 2026. > **Code-First vs Schema-First** > > NestJS 11 defaults to code-first GraphQL, generating the schema from TypeScript classes. Schema-first remains available for teams with existing `.graphql` files or SDL-based workflows. ## Setting Up NestJS GraphQL with Apollo Server NestJS integrates with [Apollo Server](https://www.apollographql.com/docs/apollo-server/) through the `@nestjs/graphql` package. The setup differs based on the chosen approach—code-first generates SDL from decorators, while schema-first parses `.graphql` files directly. ```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: generates schema sortSchema: true, // Alphabetical ordering for readability playground: process.env.NODE_ENV !== 'production', // Disable in prod introspection: process.env.NODE_ENV !== 'production', }), ], }) export class AppModule {} ``` The `autoSchemaFile` option enables code-first mode. Setting it to `true` generates the schema in memory without writing to disk—useful for serverless deployments where filesystem access may be restricted. ## Defining GraphQL Types with Code-First Decorators Code-first defines GraphQL types using TypeScript classes decorated with `@ObjectType()`. Each field uses `@Field()` to specify its GraphQL type and nullability. ```typescript // user.entity.ts import { ObjectType, Field, ID, Int } from '@nestjs/graphql'; @ObjectType({ description: 'Application user' }) // Description appears in schema docs export class User { @Field(() => ID) // Maps to GraphQL ID scalar id: string; @Field() email: string; @Field({ nullable: true }) // Optional field in GraphQL displayName?: string; @Field(() => Int, { defaultValue: 0 }) postCount: number; @Field(() => [Post], { nullable: 'itemsAndList' }) // Both list and items can be null posts?: Post[]; // Fields without @Field() are excluded from GraphQL schema passwordHash: string; } ``` The `nullable` option accepts three values: `true` (field is optional), `'items'` (list items can be null), and `'itemsAndList'` (both the list and items can be null). This granularity matches [GraphQL's nullability semantics](https://graphql.org/learn/schema/#lists-and-non-null) precisely. ## Building Resolvers for Queries and Mutations Resolvers handle incoming GraphQL operations. NestJS uses `@Resolver()` to mark a class as a resolver, with method decorators specifying operation types. ```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) // Binds resolver to User type for field resolution export class UsersResolver { constructor(private readonly usersService: UsersService) {} @Query(() => [User], { name: 'users' }) // Explicit query name 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); } } ``` The `@Resolver(() => User)` decorator establishes context for `@ResolveField()` decorators, enabling field-level resolution for computed or related data. ## Input Types and Validation with class-validator GraphQL input types define mutation payloads. Combining `@InputType()` with [class-validator](https://github.com/typestack/class-validator) decorators enables schema-level and runtime validation. ```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: 'Invalid email format' }) email: string; @Field() @MinLength(8, { message: 'Password must be at least 8 characters' }) @Matches(/[A-Z]/, { message: 'Password must contain uppercase letter' }) password: string; @Field({ nullable: true }) @IsOptional() @MinLength(2) displayName?: string; } ``` Enable validation globally by adding the `ValidationPipe` in `main.ts`. GraphQL errors include validation messages in the `extensions` field, maintaining client compatibility. ## Resolving Related Data with @ResolveField and DataLoader Field resolvers handle relationships between types. Without optimization, fetching a list of users with their posts triggers N+1 queries—one for users, then one per user for posts. ```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 batches requests: one query for all user IDs 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 batches and caches requests within a single GraphQL operation. For NestJS, scope the DataLoader to request level using `@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 }) // New instance per request export class PostsLoader { constructor(private readonly postsService: PostsService) {} public readonly batchByUserId = new DataLoader( async (userIds: readonly string[]) => { // Single query: SELECT * FROM posts WHERE user_id IN (...) const posts = await this.postsService.findByUserIds([...userIds]); // Map results back to input order 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 reduces N+1 queries to a single batched query, critical for GraphQL APIs where clients control query depth. For deeper coverage of NestJS architecture patterns, see the [NestJS Modules & Dependency Injection interview questions](/technologies/node-nestjs/interview-questions/nestjs-modules-di). ## Subscriptions for Real-Time Data GraphQL subscriptions push data to clients over WebSocket connections. NestJS uses the `graphql-ws` library, which implements the [GraphQL over WebSocket protocol](https://github.com/enisdenjo/graphql-ws). ```typescript // app.module.ts - Enable subscriptions GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, subscriptions: { 'graphql-ws': true, // Modern protocol 'subscriptions-transport-ws': false, // Deprecated legacy protocol }, }), ``` ```typescript // posts.resolver.ts import { Resolver, Subscription } from '@nestjs/graphql'; import { PubSub } from 'graphql-subscriptions'; import { Post } from './post.entity'; const pubSub = new PubSub(); // Use Redis PubSub for multi-instance deployments @Resolver(() => Post) export class PostsResolver { @Subscription(() => Post, { filter: (payload, variables) => payload.postCreated.userId === variables.userId, // Client-side filtering }) 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 }); // Trigger subscription return post; } } ``` For production deployments across multiple instances, replace the in-memory `PubSub` with `graphql-redis-subscriptions` to broadcast events cluster-wide. ## Authentication and Authorization in GraphQL NestJS Guards work seamlessly with GraphQL resolvers. The execution context differs from REST—use `GqlExecutionContext` to extract the request. ```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; // Extract request from GraphQL context } } ``` Apply guards at the resolver or method level. Field-level authorization uses `@ResolveField()` with conditional logic based on user roles. ```typescript // users.resolver.ts @UseGuards(GqlAuthGuard) @Resolver(() => User) export class UsersResolver { @Query(() => User) me(@CurrentUser() user: User): User { return user; // Return authenticated user } @ResolveField(() => String, { nullable: true }) email(@Parent() user: User, @CurrentUser() currentUser: User): string | null { // Only return email if viewing own profile or admin if (user.id === currentUser.id || currentUser.role === 'ADMIN') { return user.email; } return null; } } ``` For complex authorization, consider [GraphQL Shield](https://github.com/dimatill/graphql-shield) or NestJS's built-in CASL integration. The [NestJS Guards and Interceptors](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) article covers these patterns in depth. ## Common NestJS GraphQL Interview Questions Technical interviews for NestJS positions frequently include GraphQL-specific questions. Here are the patterns hiring managers evaluate. **Q: How does NestJS handle the N+1 problem in GraphQL?** DataLoader batches field resolver calls within a single request. When multiple parent objects request the same field, DataLoader collects all keys, executes one batched query, and distributes results. The loader must be request-scoped to prevent cross-request caching issues. **Q: What's the difference between code-first and schema-first in NestJS GraphQL?** Code-first generates the GraphQL schema from TypeScript decorators at runtime, keeping types and schema synchronized automatically. Schema-first parses `.graphql` SDL files, requiring manual type definitions. Code-first suits TypeScript-native teams; schema-first works better when the schema is the contract between frontend and backend teams. **Q: How would you implement field-level permissions?** Three approaches exist: (1) `@ResolveField()` with conditional returns based on user context, (2) Custom decorators that check permissions before field resolution, (3) Schema directives like `@auth(requires: ADMIN)` processed by a directive transformer. The first approach offers the most flexibility; directives provide the cleanest schema documentation. **Q: Explain GraphQL context in NestJS.** The context object passes through all resolvers within a request. NestJS populates it with the HTTP request by default. Custom context is configured in `GraphQLModule.forRoot()` via the `context` option—useful for adding DataLoader instances, authenticated user, or database connections. **Q: How do subscriptions scale across multiple server instances?** In-memory PubSub only works for single-instance deployments. Multi-instance architectures require an external broker—Redis PubSub is standard. Each server instance subscribes to Redis channels; when any instance publishes an event, all instances receive it and push to connected WebSocket clients. For additional NestJS interview preparation, explore the [Middleware and Interceptors module](/technologies/node-nestjs/interview-questions/middleware-interceptors) covering request lifecycle patterns. ## Conclusion - NestJS GraphQL supports both code-first and schema-first approaches—code-first simplifies TypeScript projects, schema-first suits SDL-driven workflows - DataLoader eliminates N+1 queries by batching field resolver requests within a single operation - Request-scoped DataLoader instances prevent cache pollution across concurrent requests - `GqlExecutionContext` bridges NestJS Guards with GraphQL's resolver context - Production subscriptions require Redis PubSub for multi-instance event distribution - Field-level authorization combines `@ResolveField()` with user context checks --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial