NestJS y GraphQL en 2026: Esquemas, Resolvers y Preguntas de Entrevista
Guía completa sobre la integración de NestJS con GraphQL: enfoques code-first y schema-first, creación de resolvers, y preparación para entrevistas técnicas en 2026.

La integración de GraphQL en NestJS proporciona un enfoque estructurado para construir APIs type-safe que aprovechan los decoradores de TypeScript y el lenguaje de consultas GraphQL. Este tutorial cubre los enfoques schema-first y code-first, los patrones de resolvers, y las preguntas de entrevista que realizan los reclutadores en 2026.
NestJS 11 utiliza por defecto el enfoque code-first, generando el esquema a partir de clases TypeScript. El enfoque schema-first sigue disponible para equipos con archivos .graphql existentes o flujos de trabajo basados en SDL.
Configuración de NestJS GraphQL con Apollo Server
NestJS se integra con Apollo Server mediante el paquete @nestjs/graphql. La configuración difiere según el enfoque elegido: code-first genera el SDL desde los decoradores, mientras que schema-first parsea directamente los archivos .graphql.
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'),
sortSchema: true,
playground: process.env.NODE_ENV !== 'production',
introspection: process.env.NODE_ENV !== 'production',
}),
],
})
export class AppModule {}La opción autoSchemaFile activa el modo code-first. Establecerla en true genera el esquema en memoria sin escribir en disco, lo cual resulta útil para despliegues serverless donde el acceso al sistema de archivos puede estar restringido.
Definición de Tipos GraphQL con Decoradores Code-First
El enfoque code-first define los tipos GraphQL usando clases TypeScript decoradas con @ObjectType(). Cada campo utiliza @Field() para especificar su tipo GraphQL y su nulabilidad.
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
@ObjectType({ description: 'Usuario de la aplicación' })
export class User {
@Field(() => ID)
id: string;
@Field()
email: string;
@Field({ nullable: true })
displayName?: string;
@Field(() => Int, { defaultValue: 0 })
postCount: number;
@Field(() => [Post], { nullable: 'itemsAndList' })
posts?: Post[];
// Los campos sin @Field() se excluyen del esquema GraphQL
passwordHash: string;
}La opción nullable acepta tres valores: true (campo opcional), 'items' (los elementos de la lista pueden ser null), y 'itemsAndList' (tanto la lista como los elementos pueden ser null). Esta granularidad corresponde precisamente a la semántica de nulabilidad de GraphQL.
Construcción de Resolvers para Queries y Mutations
Los resolvers manejan las operaciones GraphQL entrantes. NestJS utiliza @Resolver() para marcar una clase como resolver y @Query() o @Mutation() para definir las operaciones.
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { User } from './user.entity';
import { UserService } from './user.service';
import { CreateUserInput } from './dto/create-user.input';
@Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@Query(() => [User], { name: 'users' })
async findAll(): Promise<User[]> {
return this.userService.findAll();
}
@Query(() => User, { name: 'user', nullable: true })
async findOne(@Args('id', { type: () => ID }) id: string): Promise<User | null> {
return this.userService.findById(id);
}
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.userService.create(input);
}
}El primer argumento de @Query() y @Mutation() define el tipo de retorno GraphQL. La opción name personaliza el nombre del campo en el esquema, útil cuando las convenciones de nomenclatura de TypeScript difieren de los estándares de GraphQL.
Creación de Input Types para las Mutations
Las mutations requieren input types para validar y tipar los datos entrantes. NestJS utiliza @InputType() para definir estas estructuras.
import { InputType, Field } from '@nestjs/graphql';
import { IsEmail, MinLength, IsOptional } from 'class-validator';
@InputType()
export class CreateUserInput {
@Field()
@IsEmail({}, { message: 'Formato de email inválido' })
email: string;
@Field()
@MinLength(8, { message: 'La contraseña debe tener al menos 8 caracteres' })
password: string;
@Field({ nullable: true })
@IsOptional()
displayName?: string;
}La integración con class-validator permite validación declarativa. El ValidationPipe de NestJS aplica automáticamente estas reglas antes de la ejecución del resolver.
Manejo de Relaciones con Field Resolvers
Los field resolvers resuelven campos específicos de manera lazy, evitando la carga de datos no solicitados. Esta técnica implementa el patrón N+1 con DataLoader.
import { Resolver, ResolveField, Parent } from '@nestjs/graphql';
import { User } from './user.entity';
import { Post } from '../post/post.entity';
import { PostService } from '../post/post.service';
@Resolver(() => User)
export class UserResolver {
constructor(private readonly postService: PostService) {}
@ResolveField(() => [Post])
async posts(@Parent() user: User): Promise<Post[]> {
return this.postService.findByUserId(user.id);
}
@ResolveField(() => Int)
async postCount(@Parent() user: User): Promise<number> {
return this.postService.countByUserId(user.id);
}
}El decorador @Parent() inyecta el objeto padre resuelto. Este enfoque permite resolución condicional basada en los campos solicitados por el cliente.
Optimización con DataLoader para Evitar el Problema N+1
El problema N+1 ocurre cuando los field resolvers ejecutan una consulta por cada elemento padre. DataLoader agrupa estas consultas en una sola operación batch.
import * as DataLoader from 'dataloader';
import { Injectable, Scope } from '@nestjs/common';
import { PostService } from './post.service';
import { Post } from './post.entity';
@Injectable({ scope: Scope.REQUEST })
export class PostLoader {
constructor(private readonly postService: PostService) {}
readonly batchByUserId = new DataLoader<string, Post[]>(async (userIds) => {
const posts = await this.postService.findByUserIds([...userIds]);
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) || []);
});
}El uso de Scope.REQUEST garantiza que se cree una nueva instancia de DataLoader para cada solicitud, evitando fugas de caché entre usuarios.
Autenticación y Autorizaciones en GraphQL
La segurización de resolvers utiliza los guards de NestJS combinados con decoradores personalizados para extraer información de autenticación.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class GqlAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return !!req.user;
}
}
// current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);El guard convierte el contexto de ejecución estándar en contexto GraphQL mediante GqlExecutionContext.create(). Esta conversión es necesaria porque GraphQL utiliza una estructura de contexto diferente a REST.
Manejo de Errores y Excepciones GraphQL
GraphQL maneja los errores de manera diferente a REST. Las excepciones se transforman en errores GraphQL estructurados con extensiones personalizables.
import { Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { GqlExceptionFilter, GqlArgumentsHost } from '@nestjs/graphql';
import { GraphQLError } from 'graphql';
@Catch(HttpException)
export class GraphqlExceptionFilter implements GqlExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const gqlHost = GqlArgumentsHost.create(host);
const response = exception.getResponse();
return new GraphQLError(
typeof response === 'string' ? response : (response as any).message,
{
extensions: {
code: this.getErrorCode(exception.getStatus()),
status: exception.getStatus(),
},
},
);
}
private getErrorCode(status: number): string {
const codes: Record<number, string> = {
400: 'BAD_REQUEST',
401: 'UNAUTHENTICATED',
403: 'FORBIDDEN',
404: 'NOT_FOUND',
};
return codes[status] || 'INTERNAL_SERVER_ERROR';
}
}Este enfoque mantiene la compatibilidad con clientes GraphQL estándar que esperan errores estructurados según la especificación GraphQL.
Subscriptions para Actualizaciones en Tiempo Real
Las subscriptions de GraphQL permiten comunicaciones en tiempo real mediante WebSocket. NestJS utiliza graphql-subscriptions para pub/sub.
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
subscriptions: {
'graphql-ws': true,
'subscriptions-transport-ws': false,
},
}),
],
})
export class AppModule {}
// notification.resolver.ts
import { Resolver, Subscription, Mutation, Args } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
const pubSub = new PubSub();
@Resolver()
export class NotificationResolver {
@Subscription(() => String, {
filter: (payload, variables) => payload.userId === variables.userId,
})
notificationReceived(@Args('userId') userId: string) {
return pubSub.asyncIterableIterator('NOTIFICATION_RECEIVED');
}
@Mutation(() => Boolean)
async sendNotification(
@Args('userId') userId: string,
@Args('message') message: string,
) {
await pubSub.publish('NOTIFICATION_RECEIVED', {
notificationReceived: message,
userId,
});
return true;
}
}El protocolo graphql-ws es el estándar moderno para subscriptions GraphQL, reemplazando al antiguo subscriptions-transport-ws que ya no recibe mantenimiento.
¿Listo para aprobar tus entrevistas de Node.js / NestJS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Preguntas de Entrevista NestJS GraphQL en 2026
Las entrevistas técnicas evalúan la comprensión de los conceptos de GraphQL y su aplicación en NestJS. Estas son las preguntas frecuentes.
¿Cuál es la diferencia entre code-first y schema-first?
Code-first genera el esquema SDL a partir de decoradores TypeScript, ofreciendo tipado end-to-end automático. Schema-first parsea archivos .graphql y genera tipos TypeScript mediante plugins. Code-first es adecuado para nuevas aplicaciones TypeScript, mientras que schema-first se adapta a proyectos con un esquema GraphQL existente o equipos familiarizados con SDL.
¿Cómo se resuelve el problema N+1 en GraphQL?
El problema N+1 se resuelve con DataLoader, que agrupa consultas individuales en operaciones batch. Cada DataLoader debe tener scope de solicitud (Scope.REQUEST) para evitar fugas de caché. La implementación batch debe retornar resultados en el orden exacto de las claves de entrada.
¿Cómo se asegura una API GraphQL en NestJS?
La seguridad combina múltiples capas: guards para autenticación a nivel de resolver, interceptors para transformación de respuestas, class-validator para validación de inputs, y throttling para protección contra abusos. La introspección y el playground deben deshabilitarse en producción.
¿Cuál es la diferencia entre Query, Mutation y Subscription?
Query lee datos sin efectos secundarios. Mutation modifica datos en el servidor. Subscription establece una conexión WebSocket para actualizaciones en tiempo real. GraphQL garantiza que las queries son idempotentes, mientras que las mutations pueden tener efectos secundarios.
¿Cómo se manejan los errores en GraphQL NestJS?
Los errores de GraphQL utilizan la clase GraphQLError con extensiones personalizadas. Los exception filters convierten las excepciones de NestJS en errores GraphQL estructurados. Los errores parciales permiten retornar datos válidos aunque algunos campos fallen.
Conclusión
La integración de NestJS con GraphQL proporciona un framework robusto para construir APIs type-safe y de alto rendimiento. El enfoque code-first simplifica el desarrollo al eliminar la duplicación entre las definiciones TypeScript y el esquema GraphQL. Patrones como DataLoader y los guards de autenticación garantizan aplicaciones escalables y seguras. El dominio de estos conceptos, combinado con una comprensión de los trade-offs entre los diferentes enfoques, prepara eficazmente para las entrevistas técnicas de Node.js en 2026.
Compartir
Artículos relacionados

NestJS y TypeORM en 2026: migraciones, relaciones y preguntas de entrevista
Dominar la integración de NestJS con TypeORM usando las migraciones de TypeORM 1.0, las relaciones entre entidades, el patrón repository y las preguntas de entrevista habituales para desarrolladores backend.

Node.js 24 en 2026: URLPattern, Permission Model y preguntas de entrevista
Node.js 24 LTS trae un Permission Model estable, URLPattern global, gestión explícita de recursos con using/await using y V8 13.6. Un análisis a fondo de las funciones que importan en producción y en entrevistas.

Microservicios con NestJS en 2026: Arquitectura gRPC, Patrones de Streaming y Preguntas de Entrevista
Guia practica sobre arquitectura de microservicios NestJS con gRPC: capas de transporte, Protocol Buffers, patrones de streaming y preguntas de entrevista para desarrolladores backend en 2026.