NestJS e GraphQL nel 2026: Schema, Resolver e Domande per Colloqui

L'integrazione di NestJS con GraphQL consente lo sviluppo di API type-safe. Questo tutorial copre gli approcci code-first e schema-first, pattern di resolver e domande da colloquio per il 2026.

NestJS e GraphQL nel 2026: Schema, Resolver e Domande per Colloqui

L'integrazione di NestJS con GraphQL offre un approccio strutturato per costruire API type-safe che sfruttano i decoratori TypeScript e il linguaggio di query di GraphQL. Questo tutorial copre sia l'approccio schema-first che code-first, i pattern dei resolver e le domande che i responsabili delle assunzioni pongono nel 2026.

Code-First vs Schema-First

NestJS 11 utilizza di default GraphQL code-first, generando lo schema dalle classi TypeScript. Schema-first rimane disponibile per i team che lavorano con file .graphql esistenti o workflow basati su SDL.

Configurazione di NestJS GraphQL con Apollo Server

NestJS si integra con Apollo Server attraverso il pacchetto @nestjs/graphql. La configurazione differisce in base all'approccio scelto—code-first genera SDL dai decoratori, mentre schema-first analizza direttamente i file .graphql.

app.module.tstypescript
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      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 {}

L'opzione autoSchemaFile abilita la modalità code-first. Impostandola su true, lo schema viene generato in memoria senza scrittura su disco—utile per deployment serverless dove l'accesso al filesystem potrebbe essere limitato.

Definire Tipi GraphQL con Decoratori Code-First

Code-first definisce i tipi GraphQL utilizzando classi TypeScript decorate con @ObjectType(). Ogni campo utilizza @Field() per specificare il suo tipo GraphQL e la nullabilità.

user.entity.tstypescript
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;
}

L'opzione nullable accetta tre valori: true (campo opzionale), 'items' (gli elementi della lista possono essere null) e 'itemsAndList' (sia la lista che gli elementi possono essere null). Questa granularità corrisponde esattamente alla semantica di nullabilità di GraphQL.

Costruire Resolver per Query e Mutation

I resolver gestiscono le operazioni GraphQL in arrivo. NestJS utilizza @Resolver() per contrassegnare una classe come resolver, con decoratori di metodo che specificano i tipi di operazione.

users.resolver.tstypescript
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<User[]> {
    return this.usersService.findAll();
  }

  @Query(() => User, { nullable: true })
  user(@Args('id', { type: () => ID }) id: string): Promise<User | null> {
    return this.usersService.findOne(id);
  }

  @Mutation(() => User)
  createUser(@Args('input') input: CreateUserInput): Promise<User> {
    return this.usersService.create(input);
  }
}

Il decoratore @Resolver(() => User) stabilisce il contesto per i decoratori @ResolveField(), abilitando la risoluzione a livello di campo per dati calcolati o correlati.

Tipi di Input e Validazione con class-validator

I tipi di input GraphQL definiscono i payload delle mutation. La combinazione di @InputType() con i decoratori class-validator abilita la validazione sia a livello di schema che a runtime.

dto/create-user.input.tstypescript
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;
}

La validazione viene abilitata globalmente aggiungendo la ValidationPipe in main.ts. Gli errori GraphQL includono i messaggi di validazione nel campo extensions, mantenendo la compatibilità con i client.

Pronto a superare i tuoi colloqui su Node.js / NestJS?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Risolvere Dati Correlati con @ResolveField e DataLoader

I field resolver gestiscono le relazioni tra tipi. Senza ottimizzazione, il recupero di una lista di utenti con i loro post attiva N+1 query—una per gli utenti, poi una per utente per i post.

users.resolver.tstypescript
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<Post[]> {
    // DataLoader batches requests: one query for all user IDs
    return this.postsLoader.batchByUserId.load(user.id);
  }

  @ResolveField(() => Int)
  async postCount(@Parent() user: User): Promise<number> {
    const posts = await this.postsLoader.batchByUserId.load(user.id);
    return posts.length;
  }
}

DataLoader raggruppa e memorizza nella cache le richieste all'interno di una singola operazione GraphQL. Per NestJS, il DataLoader viene fornito a livello di request utilizzando @Injectable({ scope: Scope.REQUEST }).

posts/posts.loader.tstypescript
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<string, Post[]>(
    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<string, Post[]>();
      posts.forEach(post => {
        const existing = postsMap.get(post.userId) || [];
        postsMap.set(post.userId, [...existing, post]);
      });
      return userIds.map(id => postsMap.get(id) || []);
    }
  );
}

DataLoader riduce le query N+1 a una singola query raggruppata, cruciale per le API GraphQL dove i client controllano la profondità delle query. Per una copertura più approfondita dei pattern architetturali NestJS, consultare le domande da colloquio su NestJS Module e Dependency Injection.

Subscription per Dati in Tempo Reale

Le subscription GraphQL inviano dati ai client tramite connessioni WebSocket. NestJS utilizza la libreria graphql-ws, che implementa il protocollo GraphQL over WebSocket.

app.module.ts - Enable subscriptionstypescript
GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  autoSchemaFile: true,
  subscriptions: {
    'graphql-ws': true, // Modern protocol
    'subscriptions-transport-ws': false, // Deprecated legacy protocol
  },
}),
posts.resolver.tstypescript
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<Post> {
    const post = await this.postsService.create(input);
    pubSub.publish('postCreated', { postCreated: post }); // Trigger subscription
    return post;
  }
}

Per deployment in produzione su più istanze, sostituire la PubSub in-memory con graphql-redis-subscriptions per trasmettere eventi a livello di cluster.

Autenticazione e Autorizzazione in GraphQL

I Guard di NestJS funzionano perfettamente con i resolver GraphQL. Il contesto di esecuzione differisce da REST—utilizzare GqlExecutionContext per estrarre la request.

guards/gql-auth.guard.tstypescript
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
  }
}

I guard vengono applicati a livello di resolver o metodo. L'autorizzazione a livello di campo utilizza @ResolveField() con logica condizionale basata sui ruoli utente.

users.resolver.tstypescript
@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;
  }
}

Per autorizzazioni complesse, considerare GraphQL Shield o l'integrazione CASL integrata in NestJS. L'articolo NestJS Guard e Interceptor tratta questi pattern in dettaglio.

Domande Comuni da Colloquio su NestJS GraphQL

I colloqui tecnici per posizioni NestJS includono frequentemente domande specifiche su GraphQL. Ecco i pattern che i responsabili delle assunzioni valutano.

D: Come gestisce NestJS il problema N+1 in GraphQL?

DataLoader raggruppa le chiamate ai field resolver all'interno di una singola richiesta. Quando più oggetti padre richiedono lo stesso campo, DataLoader raccoglie tutte le chiavi, esegue una query raggruppata e distribuisce i risultati. Il loader deve essere request-scoped per evitare problemi di caching tra le richieste.

D: Qual è la differenza tra code-first e schema-first in NestJS GraphQL?

Code-first genera lo schema GraphQL dai decoratori TypeScript a runtime, mantenendo tipi e schema sincronizzati automaticamente. Schema-first analizza file SDL .graphql, richiedendo definizioni di tipo manuali. Code-first è adatto a team nativi TypeScript; schema-first funziona meglio quando lo schema è il contratto tra team frontend e backend.

D: Come implementare permessi a livello di campo?

Esistono tre approcci: (1) @ResolveField() con ritorni condizionali basati sul contesto utente, (2) Decoratori personalizzati che verificano i permessi prima della risoluzione del campo, (3) Direttive schema come @auth(requires: ADMIN) elaborate da un trasformatore di direttive. Il primo approccio offre la massima flessibilità; le direttive forniscono la documentazione dello schema più pulita.

D: Spiega il contesto GraphQL in NestJS.

L'oggetto contesto passa attraverso tutti i resolver all'interno di una richiesta. NestJS lo popola con la richiesta HTTP di default. Il contesto personalizzato viene configurato in GraphQLModule.forRoot() tramite l'opzione context—utile per aggiungere istanze DataLoader, utente autenticato o connessioni database.

D: Come scalano le subscription su più istanze server?

PubSub in-memory funziona solo per deployment a istanza singola. Architetture multi-istanza richiedono un broker esterno—Redis PubSub è lo standard. Ogni istanza server sottoscrive i canali Redis; quando un'istanza pubblica un evento, tutte le istanze lo ricevono e lo inviano ai client WebSocket connessi.

Per ulteriore preparazione ai colloqui NestJS, esplorare il modulo Middleware e Interceptor che copre i pattern del ciclo di vita delle richieste.

Conclusione

  • NestJS GraphQL supporta sia l'approccio code-first che schema-first—code-first semplifica i progetti TypeScript, schema-first è adatto ai workflow guidati da SDL
  • DataLoader elimina le query N+1 raggruppando le richieste dei field resolver all'interno di una singola operazione
  • Le istanze DataLoader request-scoped prevengono l'inquinamento della cache tra richieste concorrenti
  • GqlExecutionContext collega i Guard NestJS con il contesto dei resolver GraphQL
  • Le subscription in produzione richiedono Redis PubSub per la distribuzione di eventi multi-istanza
  • L'autorizzazione a livello di campo combina @ResolveField() con controlli del contesto utente

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Condividi

Articoli correlati