From nestjs-plugin
GraphQL, WebSockets, and microservices patterns for NestJS. Apply only when the relevant package is in dependencies (`@nestjs/graphql`, `@nestjs/websockets`, `@nestjs/microservices`). Each section is orientation, not exhaustive — defer to NestJS docs for deep dives. Use this skill to: - Wire a GraphQL resolver (code-first) with class-validator inputs. - Build a WebSocket gateway with auth and room management. - Set up a microservice transport (TCP/RabbitMQ/NATS/Redis/Kafka) and message handlers. Do NOT use this skill for: - REST controllers (see nest-conventions + decorator-patterns). - ORM (see nest-data-layer). - Auth strategies (see security-analyst phase guidance).
How this skill is triggered — by the user, by Claude, or both
Slash command
/nestjs-plugin:nest-advancedThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers the three advanced surfaces NestJS supports beyond REST. Each is opt-in via package presence — do not introduce these surfaces speculatively.
This skill covers the three advanced surfaces NestJS supports beyond REST. Each is opt-in via package presence — do not introduce these surfaces speculatively.
@nestjs/graphql)import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql', // or `true` for in-memory
sortSchema: true,
playground: process.env.NODE_ENV !== 'production',
context: ({ req }) => ({ req }),
}),
],
})
export class AppModule {}
@ObjectType()
export class User {
@Field(() => ID)
id!: string;
@Field()
email!: string;
@Field({ nullable: true })
name?: string;
@Field(() => [Order])
orders!: Order[];
}
@Field() — exposes the property; type inferred from TypeScript when possible.@Field(() => [Order]).nullable: true for optional fields; matches TS ?: modifier.@InputType()
export class CreateUserInput {
@Field()
@IsEmail()
email!: string;
@Field()
@MinLength(8)
password!: string;
}
InputType + class-validator works alongside the global ValidationPipe for runtime checks.
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly users: UsersService) {}
@Query(() => User, { nullable: true })
user(@Args('id', { type: () => ID }) id: string) {
return this.users.findById(id);
}
@Query(() => [User])
users() {
return this.users.findAll();
}
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput) {
return this.users.create(input);
}
@ResolveField(() => [Order])
orders(@Parent() user: User) {
return this.users.ordersByUserId(user.id);
}
}
@UseGuards() on resolvers and field resolvers. Use GqlExecutionContext in guards:
@Injectable()
export class GqlAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
return !!req.user;
}
}
For custom param decorators in GraphQL, use:
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) =>
GqlExecutionContext.create(ctx).getContext().req.user,
);
@Injectable({ scope: Scope.REQUEST })
export class OrdersLoader {
constructor(private readonly orders: OrdersService) {}
readonly byUserId = new DataLoader<string, Order[]>(async (userIds) => {
const orders = await this.orders.findByUserIds([...userIds]);
return userIds.map((id) => orders.filter((o) => o.userId === id));
});
}
Inject in resolver, use in @ResolveField. Avoids N+1 on User.orders across a list query.
@nestjs/apollo supports Apollo Federation v2 via ApolloFederationDriver + @apollo/subgraph. If the project uses federation (@key directives, multiple subgraphs), defer to NestJS Federation docs — patterns are stable but extensive.
If the project uses schema-first (legacy or specific tooling), .graphql files drive types and @Resolver classes implement them via @nestjs/graphql codegen. Match what exists; don't migrate without BA approval.
@nestjs/websockets)import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: { origin: process.env.WS_ORIGIN ?? '*' },
namespace: 'chat',
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server!: Server;
handleConnection(client: Socket) {
// auth on connection — read token from handshake
}
handleDisconnect(client: Socket) {
// cleanup
}
@SubscribeMessage('message')
async onMessage(
@MessageBody() payload: SendMessageDto,
@ConnectedSocket() client: Socket,
): Promise<{ ok: true }> {
this.server.to(payload.roomId).emit('message', { from: client.id, text: payload.text });
return { ok: true };
}
}
WebSocket auth is NOT covered by HTTP guards by default. Pattern:
handshake.auth.token (Socket.io) or Sec-WebSocket-Protocol (raw WS).handleConnection(client) validates synchronously; on failure, client.disconnect(true).client.data.user populated at connect.Or use @UseGuards(WsJwtGuard) per @SubscribeMessage — guard implements canActivate reading from socket handshake.
client.join(`room:${roomId}`);
client.leave(`room:${roomId}`);
this.server.to(`room:${roomId}`).emit('event', payload);
Rooms are cheap; cleanup happens automatically on disconnect.
DTOs work the same — global ValidationPipe applies if enabled at app level. For Socket.io, payload arrives as parsed JSON; class-transformer + class-validator validate normally.
@nestjs/platform-socket.io is the default. For raw ws, install @nestjs/platform-ws and app.useWebSocketAdapter(new WsAdapter(app)). For Redis-backed pub/sub across instances, install @socket.io/redis-adapter and configure in IoAdapter subclass.
@nestjs/microservices)| Transport | Use case |
|---|---|
Transport.TCP | Simple node-to-node, no broker |
Transport.RMQ (RabbitMQ) | Reliable queues, work distribution |
Transport.NATS | High-throughput, lightweight messaging |
Transport.REDIS | Pub/sub, simple event broadcasting |
Transport.KAFKA | Event sourcing, log-based |
Transport.MQTT | IoT |
Transport.GRPC | Strongly-typed RPC, polyglot services |
// src/main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: [process.env.RABBITMQ_URL!],
queue: 'orders_queue',
queueOptions: { durable: true },
prefetchCount: 1,
},
});
await app.startAllMicroservices();
await app.listen(3000);
}
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.NATS,
options: { servers: ['nats://localhost:4222'] },
});
await app.listen();
}
// request-response (returns)
@MessagePattern({ cmd: 'find_user' })
findUser(@Payload() dto: FindUserDto, @Ctx() context: RmqContext) {
return this.users.findById(dto.id);
}
// fire-and-forget event
@EventPattern('order.placed')
async handleOrderPlaced(@Payload() event: OrderPlacedEvent) {
await this.email.sendOrderConfirmation(event);
}
@Injectable()
export class OrdersService {
constructor(@Inject('USERS_SERVICE') private readonly usersClient: ClientProxy) {}
async createOrder(userId: string, items: OrderItem[]) {
const user = await firstValueFrom(this.usersClient.send({ cmd: 'find_user' }, { id: userId }));
// ... use user
this.usersClient.emit('order.placed', { userId, items });
}
}
Register the client in module providers:
@Module({
imports: [
ClientsModule.register([
{
name: 'USERS_SERVICE',
transport: Transport.RMQ,
options: { urls: [process.env.RABBITMQ_URL!], queue: 'users_queue' },
},
]),
],
})
By default RabbitMQ auto-acks. For retry-safe handling, set noAck: false and ack manually:
@EventPattern('order.placed')
async handle(@Payload() data: any, @Ctx() ctx: RmqContext) {
const channel = ctx.getChannelRef();
const msg = ctx.getMessage();
try {
await this.process(data);
channel.ack(msg);
} catch {
channel.nack(msg, false, true); // requeue
}
}
await app.startAllMicroservices() BEFORE app.listen().MessagePattern for request-response; EventPattern for fire-and-forget. Mixing them causes hangs.@UsePipes(new ValidationPipe()) per handler or via app.useGlobalPipes() on the microservice instance.npx claudepluginhub aratkruglik/claude-sdlc --plugin nestjs-pluginProvides NestJS framework knowledge including controllers, modules, providers, dependency injection, pipes, guards, interceptors, and CLI usage for building scalable server-side applications.
Provides expert guidance on Nest.js enterprise architecture, dependency injection, decorators, middleware, guards, interceptors, pipes, testing, database integration, and authentication.
Expert guidance on Nest.js architecture including modules, DI, decorators, guards, interceptors, pipes, testing with Jest, and authentication.