GraphQL enables you to interact with your data by defining a schema that specifies the types, queries, mutations, and subscriptions. The key component that handles GraphQL operations in NestJS is the Resolver. Resolvers are responsible for fetching data for queries and performing actions for mutations and subscriptions.
In this module, we will break down how to implement Resolvers, define Queries, Mutations, and Subscriptions in NestJS, and understand their roles in building a complete GraphQL API.
Table of Contents
- What are Resolvers in GraphQL?
- Queries in NestJS
- Mutations in NestJS
- Subscriptions in NestJS
- Resolver Example in NestJS
- Best Practices
- Conclusion
What are Resolvers in GraphQL?
In GraphQL, Resolvers are functions that are responsible for fetching the data for the fields specified in a query, mutation, or subscription. When a request is made to a GraphQL server, the resolver determines how to resolve that request based on the fields requested.
Resolvers are connected to the GraphQL schema. They perform the necessary actions (like fetching data from a database or calling external APIs) and return the appropriate data for the client.
In NestJS, resolvers are decorated with @Resolver()
and are responsible for implementing the query, mutation, and subscription logic.
Queries in NestJS
Defining Queries
A Query in GraphQL is used to fetch data. It is analogous to a GET request in REST APIs. In NestJS, you define queries using the @Query()
decorator.
Query Resolvers
Let’s look at how to define a simple query that retrieves data.
tsCopyEdit// user.model.ts
import { ObjectType, Field, Int } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field(type => Int)
id: number;
@Field()
name: string;
@Field()
email: string;
}
Now, let’s create a query resolver that fetches user data:
tsCopyEdit// user.resolver.ts
import { Resolver, Query, Args } from '@nestjs/graphql';
import { User } from './user.model';
import { UserService } from './user.service';
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Query(returns => User)
async getUser(@Args('id') id: number): Promise<User> {
return this.userService.findOne(id);
}
}
In this example:
- The
@Query()
decorator defines a query operation. - The
@Args()
decorator defines the parameters for the query.
This query fetches a User
by their id
. The resolver method (getUser
) interacts with the service layer to fetch the data.
Mutations in NestJS
Defining Mutations
A Mutation in GraphQL is used to modify data (create, update, or delete). It is similar to POST, PUT, or DELETE requests in REST APIs. Mutations can return values, like queries, but they usually perform side effects (e.g., database updates).
Mutation Resolvers
Here’s how to define a mutation in NestJS:
tsCopyEdit// user.resolver.ts
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { User } from './user.model';
import { UserService } from './user.service';
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Mutation(returns => User)
async createUser(
@Args('name') name: string,
@Args('email') email: string
): Promise<User> {
return this.userService.create(name, email);
}
}
In this example:
- The
@Mutation()
decorator defines a mutation operation. - The
@Args()
decorator defines the input parameters for the mutation.
The createUser
mutation creates a new user by invoking the service layer and passing the name
and email
.
Subscriptions in NestJS
Defining Subscriptions
A Subscription in GraphQL is used for real-time updates. Unlike queries and mutations, subscriptions are long-lived operations that maintain a connection with the client and push updates when data changes. Subscriptions are often used for scenarios like messaging apps or live feeds.
Subscription Resolvers
To implement subscriptions, we use the @Subscription()
decorator in NestJS:
tsCopyEdit// user.resolver.ts
import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { User } from './user.model';
import { UserService } from './user.service';
const pubSub = new PubSub();
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Mutation(returns => User)
async createUser(
@Args('name') name: string,
@Args('email') email: string
): Promise<User> {
const user = await this.userService.create(name, email);
pubSub.publish('userCreated', { userCreated: user });
return user;
}
@Subscription(returns => User, {
resolve: value => value.userCreated,
})
userCreated() {
return pubSub.asyncIterator('userCreated');
}
}
In this example:
- The
createUser
mutation creates a new user and then triggers theuserCreated
subscription, notifying all subscribed clients. - The
@Subscription()
decorator defines a subscription operation. - The
PubSub
instance is used to manage real-time notifications.
The asyncIterator
listens for changes (in this case, the userCreated
event) and pushes updates to the subscribers.
Resolver Example in NestJS
Let’s put it all together in a full example that includes a query, mutation, and subscription.
tsCopyEdit// user.resolver.ts
import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { User } from './user.model';
import { UserService } from './user.service';
const pubSub = new PubSub();
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Query(returns => User)
async getUser(@Args('id') id: number): Promise<User> {
return this.userService.findOne(id);
}
@Mutation(returns => User)
async createUser(
@Args('name') name: string,
@Args('email') email: string
): Promise<User> {
const user = await this.userService.create(name, email);
pubSub.publish('userCreated', { userCreated: user });
return user;
}
@Subscription(returns => User, {
resolve: value => value.userCreated,
})
userCreated() {
return pubSub.asyncIterator('userCreated');
}
}
Best Practices
- Separation of concerns: Keep your resolvers focused on data fetching and mutation logic. Complex business logic should be handled in services or other layers.
- Error handling: Always include error handling in mutations to ensure your GraphQL API behaves predictably.
- Subscriptions: When implementing subscriptions, make sure you have a clear understanding of how WebSockets work, as they are the backbone of subscription functionality.
- Authentication and Authorization: Protect sensitive queries and mutations using guards, and ensure that only authorized users can access or modify certain data.
Conclusion
In this module, we have learned how to implement Resolvers, Queries, Mutations, and Subscriptions in NestJS using GraphQL. These fundamental concepts enable you to build dynamic and real-time APIs with ease.
By defining resolvers to handle queries, mutations, and subscriptions, you can create powerful and flexible GraphQL APIs that allow clients to interact with your data in a more efficient and streamlined manner.