Quick start

GraphQL is a powerful API query language that is an elegant way to solve many common problems in REAT apis. GraphQL in conjunction with TypeScript can help you develop better GraphQL query type security and provide end-to-end input.

The installation

$ npm i @nestjs/graphql graphql-tools graphql apollo-server-express
Copy the code

An overview of

Nest provides two ways to build GraphQL projects: Code First and Schema First. You can choose whichever approach works best for you. The body of the chapter described below is divided into two parts. If you adopt Code First, you should follow one, and if you adopt Schema First, you should use the other.

The Code First method uses decorators and TypeScript classes to generate a corresponding GraphQL schema. This is useful if you prefer to use TypeScript alone to avoid context switching between language grammars.

In the Schema First method, the source of the facts is the GraphQL SDL file. SDL is a language-independent way to share schema files between platforms. Nest automatically generates your TypeScript definition files based on the GraphQL schema. Reduce the need to write redundant boilerplate code.

Getting started with GraphQL & TypeScript

Once the package is installed, we can import GraphQLModule and configure it using the forRoot() static method.

@@filename()
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot({}),
  ],
})
export class AppModule {}

Copy the code

The forRoot() method takes an options object as an argument. These Option objects are passed to the underlying Apollo instance. For example, if you want to disable your playground and turn off debug mode, can you check it out?

@@filename()
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot({
      debug: false.playground: false,})]})export class AppModule To access the playground, you need to configure and run a basic GraphQL server.Copy the code

As mentioned above, these options are forwarded to the ApolloServer constructor.

GraphQL playground

Playground is a graphical, interactive, browser-built GraphQL IDE that can be used on the same URL as the GraphQL server itself by default. To access Playground, you need to configure and run a basic GraphQL server.

Multiple endpoints

Another useful feature of the @nestjs/ GraphQL module is the ability to serve multiple endpoints simultaneously. This lets you decide which modules should be included in which endpoint. By default, GraphQL searches for parsers throughout the application. To limit this to a subset of modules, use the include attribute.

GraphQLModule.forRoot({
  include: [CatsModule],
}),
Copy the code

Code first

In the Code First method, you use decorators and TypeScript classes to generate the corresponding GraphQL schema.

To use the code First method, add the autoSchemaFile property to the options object:

GraphQLModule.forRoot({
  autoSchemaFile: join(process.cwd(), 'src/schema.gql'),}),Copy the code

The autoSchemaFile property value is the path to create the automatically generated schema. Alternatively, the schema can be dynamically generated in memory. To enable this feature, set the autoSchemaFile property to true.

GraphQLModule.forRoot({
  autoSchemaFile: true,}).Copy the code

By default, the types in the generated schema are sorted in the order they are defined in the included module. To sort schemas lexicographically, set the sortSchema property to true.

GraphQLModule.forRoot({
  autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
  sortSchema: true,}).Copy the code

Schema first

In the Schema First method, you first add a typePaths property to the Option object. The typePaths property refers to the shcema definition file that indicates where GraphQLModule should look for the GraphQL SDL you are about to write. These files will be merged in memory; This allows you to split shcema into several files and locate them near their parser.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'],}).Copy the code

You also usually need TypeScript definitions (classes and interfaces) to correspond to GraphQL’s SDL type, which is redundant and tedious to create manually. Every change we make in SDL forces us to adjust TypeScript definitions. To solve this problem, the @Nestjs/GraphQL package automatically generates TypeScript definitions from the (AST) abstract syntax tree. In order to enable this property, Define the Definitions Options attribute in the GraphQLModule.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'].definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),}}),Copy the code

The Path attribute of the Definitions Options object specifies where to save generated TypeScript output. By default, all generated TypeScript types are created as interfaces. To generate classes, specify the value of the outputAs attribute as class.

GraphQLModule.forRoot({
  typePaths: ['./**/*.graphql'].definitions: {
    path: join(process.cwd(), 'src/graphql.ts'),
    outputAs: 'class',}}),Copy the code

The above method dynamically generates TypeScript definitions each time an application is launched. Or, better yet, build a simple script to generate these files on demand. A simple example: Suppose we create the following script as generate-typings.ts.

import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';

const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'].path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class'});Copy the code

Now we can execute the script with the following example:

ts-node generate-typings
Copy the code

Sometimes, we need to be able to automatically recognize the content and make corresponding adjustments when the specified files (files that meet the requirements) change according to the rules. At this time, we need to set the listening mode. Pass the Watch option to the generate() method.

definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'].path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class'.watch: true});Copy the code

To automatically generate an additional __typename field for each object type, enable the emitTypenameField option.

definitionsFactory.generate({
  / /... .
  emitTypenameField: true});Copy the code

To generate parsers (query, variation, subscription) as normal fields with no arguments, enable the skipResolverArgs option.

definitionsFactory.generate({
  / /... .
  skipResolverArgs: true});Copy the code

Accessing generated schema

In some cases, such as end-to-end testing, you may want to refer to the generated schema objects, where graphQL objects can be used to run queries without using any HTTP listeners.

You can use the GraphQLSchemaHost class to access the generated schema.

const { schema } = app.get(GraphQLSchemaHost);
Copy the code

Async configuration

Use the forRootAsync() method when you need to pass module options asynchronously rather than statically. As with most dynamic modules, Nest provides several techniques to handle asynchronous configuration.

The first way is to use the factory method:

GraphQLModule.forRootAsync({
  useFactory: () = > ({
    typePaths: ['./**/*.graphql'],})}),Copy the code

Like other factory providers, our factory functions can be asynchronous and inject dependencies.

GraphQLModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    typePaths: configService.getString('GRAPHQL_TYPE_PATHS'),}),inject: [ConfigService],
}),
Copy the code

Alternatively, you can configure GraphQLModule using not only factory methods but also classes, as shown below:

GraphQLModule.forRootAsync({
  useClass: GqlConfigService,
}),
Copy the code

The above construct instantiates GqlConfigService in GraphQLModule, which is used to create the option object. Note that in this example,GqlConfigService must implement the GqlOptionsFactory interface, as shown below: GraphQLModule calls the createGqlOptions() method on the instantiated object of the supplied class.

@Injectable()
class GqlConfigService implements GqlOptionsFactory {
  createGqlOptions(): GqlModuleOptions {
    return {
      typePaths: ['./**/*.graphql']}; }}Copy the code

If you want to reuse an existing Option provider instead of creating a private backup in the GraphQLModule, use the useExisting syntax.

GraphQLModule.forRootAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
}),
Copy the code

Resolvers

Concept: Resolvers provide instructions to transform GraphQL operations (queries, mutates, or subscriptions) into data. They return data of the same structure that we specified in the schema. Either synchronous or a Promise that resolves into the result of that format. Typically, you can create a Resolvers map manually. On the other hand, the @nestjs/ GraphQL package lets you automatically generate a parser map using metadata provided by the annotated class’s decorator. To demonstrate the process of creating the GraphQL API using package features, we will create a simple authors API.

Code first

With the Code First approach, we didn’t follow the typical procedure of writing GraphQL SDL by hand to create the GraphQL schema. Instead, we use TypeScript decorators to generate SDL from TypeScript class definitions. The @nestjs/ GraphQL package reads the metadata defined by the decorator and automatically generates the schema.

Object types

Most of the definitions in the GraphQL schema are object types. Each object type you define should represent a domain object with which your application might interact. An example: THE API definition needs to be able to get a list of authors and their posts. So we need to define Author and Post types to support this functionality.

If we use the Schema First approach, we will use SDL to define a schema like this:

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}
Copy the code

In this case, using the Code First method, we use TypeScript classes to define schemas and use TypeScript decorators to annotate the fields of those classes. In the Code First method, the equivalent of the above SDL is:

@@filename(authors/models/author.model)
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Post } from './post';

@ObjectType()
export class Author {
  @Field(type= > Int)
  id: number;

  @Field({ nullable: true}) firstName? : string; @Field({nullable: true}) lastName? : string; @Field(type= > [Post])
  posts: Post[];
}
Copy the code

We had to explicitly use the @Field() decorator in the schema definition class to provide metadata about the GraphQL type and optionality of each Field.

The Author object type, like any class, consists of a collection of fields, each declaring a type. The field type corresponds to the GraphQL type. The GraphQL type of the field can be another object type or a scalar type. GraphQL scalar types are primitives that parse into a single value (such as ID, String, Boolean, or Int).

The Author object type definition above will cause Nest to generate SDL as shown above:

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}
Copy the code

The @field () decorator takes an optional type function (for example, type => Int) and an optional options object. Type functions are required when there may be ambiguity between the TypeScript type system and the GraphQL type system. Specifically: String and Boolean are not required; Number is required (it must map to a GraphQL Int or Float), and the type function should simply return the desired GraphQL type.

Options objects can have any of the following key/value pairs:

  • nullable: Specifies whether a field is empty (inSDLIn, each field defaults to non-empty);boolean
  • description: Used to set the field description.string
  • deprecationReason: marks a deprecated field;string

Here’s an example:

@Field({ description: `Book title`.deprecationReason: 'Not useful in v2 schema'})
title: string;
Copy the code

When the field is an array, we must manually specify the array type in the type function of the field() decorator, as follows:

@Field(type= > [Post])
posts: Post[];
Copy the code

Using array square brackets ([]), we can indicate the depth of the array. For example, using [[Int]] will represent a matrix of integers.

To declare the items of an array (not the array itself) to be nullable, set the Nullable property to ‘items’.

@Field(type= > [Post], { nullable: 'items' })
posts: Post[];
Copy the code

If the array and its items are nullable, set nullable to ‘itemsAndList’.

Now that the Author object type has been created, let’s define the Post object type.

@@filename(posts/models/post.model)
import { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType(a)export class Post {
  @Field(type= > Int)
  id: number;

  @Field(a)title: string;

  @Field(type= > Int, { nullable: true}) votes? :number;
}
Copy the code

The Post object type will generate the GraphQL schema in SDL in the following parts:

type Post {
  id: Int!
  title: String!
  votes: Int
}
Copy the code

Code first resolver

We have defined objects that already exist in the data graph, but the client has no way to interact with them yet. To solve this problem, we need to create a parser class. In code First’s approach, the parser class both defines the parser function and generates the query type. This will become clear when we work with the following example:

@@filename(authors/authors.resolver)
@Resolver(of= > Author)
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService,
  ) {}

  @Query(returns= > Author)
  async author(@Args('id', { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField(a)async posts(@Parent() author: Author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id }); }}Copy the code

All decorators (e.g. @resolver, @resolveField, @args, etc.) are exported from the @nestjs/ graphQL package.

You can define more than one parser class. Nest will incorporate these at run time. For more information about code organization, see the modules section below.

The logic inside the AuthorsService and PostsService classes can be as simple or as complex as needed. The main purpose of this example is to show how parsers are constructed and how they interact with other providers.

In the above example, we created AuthorsResolver, which defines a query resolver function and a field resolver function. To create a parser, we create a class with a parser function as a method and annotate the class with the @resolver () decorator.

In this example, we define a query handler to get the Author object based on the ID sent in the request. To specify this method as a Query handler, use the @Query() decorator.

The argument passed to the @resolver () decorator is optional, but comes into play when our graph becomes unimportant. It is used to provide a parent object for the field resolver function to use when traversing the object graph.

In our case, because the class contains a field parser function (the Posts attribute for the Author object type), We must provide the @resolver () decorator with a value indicating which class is the parent type (the corresponding ObjectType class name) of all field parsers defined in this class. It is clear from this example that you must access the parent object when writing a field parser function. In this example, we populate the Posts array of author with a field parser that calls a service that takes the AUTHOR’s ID as an argument. Therefore, the parent object needs to be identified in the @resolver () decorator. Note the corresponding use of the @parent () method parameter decorator, and then extract the reference to the Parent object in the field parser.

We can define multiple @Query() parser functions (both in this class and in any other parser class) that will be aggregated into a single Query type definition in the generated SDL along with the appropriate entries in the parser map. This allows us to define queries that are close to the Module and Servicce they use and keep them well organized within the module.

Query type names

In the example above, the @Query() decorator generates a GraphQL schema Query type name based on the method name. As an example, consider the following structure from the above example:

@Query(returns= > Author)
async author(@Args('id', { type: () => Int }) id: number) {
  return this.authorsService.findOneById(id);
}
Copy the code

This will generate the following entry for the Author query in our schema:

typeQuery { author(id: Int!) : Author }Copy the code

In general, we tend to decouple these names; Rather than still using author as the query type name, we prefer to use a name like getAuthor() as the query handler method, The same applies to our domain field resolvers, which we can easily do by passing the map name as an argument to the @query () and @resolveField () decorators, as shown below:

@@filename(authors/authors.resolver)
@Resolver(of= > Author)
export class AuthorsResolver {
  constructor(private authorsService: AuthorsService, private postsService: PostsService,) {}

  @Query(returns= > Author, { name: 'author' })
  async getAuthor(@Args('id', { type: () => Int }) id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField('posts'.returns= > [Post])
  async getPosts(@Parent() author: Author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id }); }}Copy the code

The above getAuthor processing method will generate the following GraphQL schema part in SDL.

type Query { author(id: Int!) : Author }Copy the code

Query decorator options

The @query () decorator object accepts many key-value pairs:

  • name: Parameter name;String
  • description: used to generateGraphQLDescription of schema documents;String
  • deprecationReason: Sets query metadata to show deprecated queries;String
  • nullable: queries whether a null data response can be returned.boolean.'items'.'itemsAndList'

Args decorator options

Use the @args () decorator to extract parameters from the request for use in method handlers.

In general, the @args () decorator will be simple and will not require an object argument like the getAuthor() method above. For example, if the identifier is of type string, the following construct will suffice, simply extracting the named field from the GraphQL request to use as the method argument.

@Args('id') id: string
Copy the code

In the case of getAuthor(), numeric types are used, which presents a challenge. The TypeScript Number type doesn’t give us enough information to describe the type GraphQL expects to represent (the Number type of GraphQL corresponds to an Int or Float). Therefore, type references must be passed explicitly. We do this by passing the second argument to the Args() decorator.

@Query(returns= > Author, { name: 'author' })
async getAuthor(@Args('id', { type: () => Int }) id: number) {
     return this.authorsService.findOneById(id);
}
Copy the code

The options object allows us to specify the following optional key-value pairs:

  • typeReturn a function of type GraphQL
  • defaultValueDefault value:
  • description: Description metadata; string
  • deprecationReason: Discards a field and provides metadata to explain why; string
  • nullable: Indicates whether the field can be empty

The query handler method can take multiple arguments, assuming we want to get an author based on its firstName and lastName. In this case, we can call @args twice

getAuthor(
 @Args('firstName', { nullable: true}) firstName? : string, @Args('lastName', { defaultValue: ' '}) lastName? : string,) {}
Copy the code

Dedicated arguments class

With inline @args () calls, code like the above example gets bloated. Instead, you can create a dedicated GetAuthorArgs argument class and access it in the Handler method, as shown below:

@Args() args: GetAuthorArgs
Copy the code

Use @argstype () to create the GetAuthorArgs class:

@@filename(authors/dto/get-author.args)
import { MinLength } from 'class-validator';
import { Field, ArgsType } from '@nestjs/graphql';

@ArgsType()
class GetAuthorArgs {
  @Field({ nullable: true}) firstName? : string; @Field({defaultValue: ' ' })
  @MinLength(3)
  lastName: string;
}
Copy the code

This will generate the following parts of the GraphQL schema in SDL:

type Query {
  author(firstName: String.lastName: String = ' '): Author
}
Copy the code

Class inheritance

You can use standard TypeScript class inheritance to create base classes with general-purpose utility type features that can be extended. For example, you might have a set of parameters related to paging, which often includes the standard offset and limit fields. There are other specific types of index fields, and you can set the class hierarchy as follows:

Parameter types of the base class:

@ArgsType(a)class PaginationArgs {
  @Field((type) = > Int)
  offset: number = 0;

  @Field((type) = > Int)
  limit: number = 10;
}
Copy the code

Type-specific subclasses of the base @argstype () class:

@ArgsType(a)class GetAuthorArgs extends PaginationArgs {
  @Field({ nullable: true}) firstName? :string;

  @Field({ defaultValue: ' ' })
  @MinLength(3)
  lastName: string;
}
Copy the code

The same method applies to @objectType () objects. Define generic attributes on base classes:

@ObjectType(a)class Character {
  @Field((type) = > Int)
  id: number;

  @Field(a)name: string;
}
Copy the code

Add a specific type of attribute to a subclass:

@ObjectType(a)class Warrior extends Character {
  @Field(a)level: number;
}
Copy the code

You can also use inheritance in the parser. You can ensure type safety by combining inheritance with TypeScript generics. For example, to create a base class with the generic findAll query, you can use a construct like this:

function BaseResolver<T extends Type<unknown> > (classRef: T) :any {
  @Resolver({ isAbstract: true })
  abstract class BaseResolverHost {
    @Query((type) = > [classRef], { name: `findAll${classRef.name}` })
    async findAll(): Promise<T[]> {
      return[]; }}return BaseResolverHost;
}
Copy the code

Note the following points:

  • An explicit return type is required, otherwiseTypeScriptcomplainsprivateThe use of class definitions. Suggestion: Define the interface instead of using itany.
  • Typefrom@nestjs/commonPackage import
  • isAbstract: trueProperty representation should not be generated for this classSDL(Schema Definition Language statements), note that you can also set this property for other types to suppressSDLGenerated.

Here’s how to generate a concrete subclass of BaseResolver:

@Resolver((of) = > Recipe)
export class RecipesResolver extends BaseResolver(Recipe) {
  constructor(private recipesService: RecipesService) {
    super();
  }
}
Copy the code

This construct will generate the following SDL:

type Query {
  findAllRecipe: [Recipe!] ! }Copy the code

Generics

We saw the use of generics above. This powerful TypeScript feature can be used to create useful abstractions. Here’s an example:

import { Field, ObjectType, Int } from '@nestjs/graphql';
import { Type } from '@nestjs/common';

export function Paginated<T> (classRef: Type<T>) :any {
  @ObjectType(`${classRef.name}Edge`)
  abstract class EdgeType {
    @Field((type) = > String)
    cursor: string;

    @Field((type) = > classRef)
    node: T;
  }

  @ObjectType({ isAbstract: true })
  abstract class PaginatedType {
    @Field((type) = > [EdgeType], { nullable: true })
    edges: EdgeType[];

    @Field((type) = > [classRef], { nullable: true })
    nodes: T[];

    @Field((type) = > Int)
    totalCount: number;

    @Field(a)hasNextPage: boolean;
  }
  return PaginatedType;
}
Copy the code

With the base class defined above, we can now easily create specialized types that inherit this behavior. Such as:

@ObjectType(a)class PaginatedAuthor extends Paginated(Author) {}
Copy the code

Schema first

As described in the previous chapter, in the Schema First approach, we first define Schema types manually in SDL, considering the following SDL type definitions.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

type Post {
  id: Int!
  title: String!
  votes: Int
}

typeQuery { author(id: Int!) : Author }Copy the code

Schema first resolver

The above schema exposes a query – author(id: Int!) : author.

Now let’s create an AuthorsResolver class to parse the author query:

@@filename(authors/authors.resolver)
@Resolver('Author')
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService,
  ) {}

  @Query(a)async author(@Args('id') id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField(a)async posts(@Parent() author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id }); }}Copy the code

The @resolver () decorator is required. It takes an optional string argument with the class name. This class name is needed whenever the class contains the @resolveField () decorator to inform Nest that the decorator method is associated with the parent type (the Author type in the current example). Alternatively, instead of setting @resolver () at the top of the class, do this for each method:

@Resolver('Author')
@ResolveField(a)async posts(@Parent() author) {
  const { id } = author;
  return this.postsService.findAll({ authorId: id });
}
Copy the code

In this case, if you have multiple @resolvefield () decorators in a class, you must add @resolver () to each one, which may not seem like a best practice.

In the example above, the @Query() and @resolveField () decorators are associated with the GraphQL schema type based on the method name. For example, consider the following structure from the example above:

@Query(a)async author(@Args('id') id: number) {
  return this.authorsService.findOneById(id);
}
Copy the code

This will generate the following for the Author query in our schema:

typeQuery { author(id: Int!) : Author }Copy the code

In general, we prefer to decouple these, using names like getAuthor() or getPosts() for our parser methods. We can easily do this by passing the map name as an argument to the decorator, as shown below:

@@filename(authors/authors.resolver)
@Resolver('Author')
export class AuthorsResolver {
  constructor(
    private authorsService: AuthorsService,
    private postsService: PostsService,
  ) {}

  @Query('author')
  async getAuthor(@Args('id') id: number) {
    return this.authorsService.findOneById(id);
  }

  @ResolveField('posts')
  async getPosts(@Parent() author) {
    const { id } = author;
    return this.postsService.findAll({ authorId: id }); }}Copy the code

Generating types

Assuming we use the Schema First method and enable the type generation feature, once you start the project it will automatically generate the following files, for example:

export class Author {
  id: number; firstName? :string; lastName? :string; posts? : Post[]; }export class Post {
  id: number;
  title: string; votes? :number;
}

export abstract class IQuery {
  abstract author(id: number): Author | Promise<Author>;
}
Copy the code

By generating classes (rather than the default technique of generating interfaces), declarative validation decorators can be used in conjunction with the Schema First approach, which is a very useful technique (read more). For example, you can add a class validator decorator to the generated CreatePostInput class, as shown below, to enforce minimum and maximum string lengths on the title field.

import { MinLength, MaxLength } from 'class-validator';

export class CreatePostInput {
  @MinLength(3)
  @MaxLength(50)
  title: string;
}
Copy the code

GraphQL argument decorators

We can use a dedicated decorator to access the standard GraphQL parser parameters. Here’s how Nest decorators compare to the generic Apollo parameters they represent.

  • root: an object containing the result returned by the parser on the parent field or, in the case of the top-level query field, the rootValue passed from the server configuration.
  • context: An object shared by all parsers in a particular query; Typically used to contain the status of each request.
  • info: An object that contains information about the execution status of the query.
  • args: An object whose arguments are passed to fields in the query.

Module

After completing the above steps, we have declaratively specified all the information needed to generate the parser map for GraphQLModule, which uses reflection to introspect the metadata provided through the decorator and automatically convert the class to the correct parser map.

The other thing you need to be careful about is providing the parser class (AuthorsResolver) and importing the module somewhere (AuthorsModule) so Nest can take advantage of it.

We can do this in the AuthorsModule, which also provides other services needed in this context. Make sure you import AuthorsModule somewhere.

@@filename(authors/authors.module.ts)
@Module({
  imports: [PostsModule],
  providers: [AuthorsService, AuthorsResolver],
})
export class AuthorsModule {}
Copy the code

Mutations

Concept: Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data. In REST, any request can end up having a side effect on the server, but best practice suggests that we should not modify the data in a GET request. GraphQL is similar — technically, any query can write data. However, as with REST, it is recommended to follow the convention that any operation that causes a write should be explicitly sent via mutation.

The official Apollo documentation uses the upvotePost() mutation example. This mutation implements a method that increases the value of the post vote attribute. To create an equivalent mutation in Nest, we will use the @mutation () decorator.

Code first

Let’s add another method to the AuthorResolver used in the previous section.

@Mutation(returns= > Post)
async upvotePost(@Args({ name: 'postId'.type: () => Int }) postId: number) {
  return this.postsService.upvoteById({ id: postId });
}
Copy the code

This causes the following GraphQL schema to be generated in SDL:

typeMutation { upvotePost(postId: Int!) : Post }Copy the code

The upvotePost() method takes postId (Int) as an argument and returns an updated Post entity. For reasons explained in the parser section, we must explicitly set the expected type.

If mutation needs to accept an object as a parameter, we can create an input type. An input type is a special object type that can be passed in as a parameter. To declare an InputType, use the @inputtype () decorator.

import { InputType, Field } from '@nestjs/graphql';
@InputType(a)export class UpvotePostInput {
  @Field(a)postId: number;
}
Copy the code

We can then use this type in the parser class:

@Mutation(returns= > Post)
async upvotePost(
  @Args('upvotePostData') upvotePostData: UpvotePostInput,
) {}
Copy the code

Schema first

Let’s extend the AuthorResolver used in the previous section

@Mutation(a)async upvotePost(@Args('postId') postId: number) {
  return this.postsService.upvoteById({ id: postId });
}
Copy the code

Note that the above has been transferred to PostsService, we assume that the business logic PostsService kind of internal logic can be simple or complex according to need. The main purpose of this example is to show how parsers interact with other providers.

The final step is to add our variation to the existing type definition.

type Author {
  id: Int!
  firstName: String
  lastName: String
  posts: [Post]
}

type Post {
  id: Int!
  title: String
  votes: Int
}

typeQuery { author(id: Int!) : Author }typeMutation { upvotePost(postId: Int!) : Post }Copy the code

Subscriptions

Concept: In addition to retrieving data using queries and modifying data using mutations, the GraphQL specification supports a third type of operation called subscription. GraphQL subscriptions are a way to push data from the server to clients that choose to listen for live messages from the server. Subscriptions are similar to queries in that they specify a set of fields to be delivered to the client, but instead of returning a single answer immediately, a channel is opened and the results sent to the client each time a specific event occurs on the server.

A common use case for subscriptions is to notify clients of specific events, such as the creation of new objects, updated fields, and so on.

Enable subscriptions with Apollo driver

Enable the subscription, please send installSubscriptionHandlers attribute set to true.

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  installSubscriptionHandlers: true,}).Copy the code

To switch to using the GraphQL-WS package, use the following configuration:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  subscriptions: {
    'graphql-ws': true}}),Copy the code

Code first

To create subscriptions using the code-first approach, we used the @Subscription() decorator (exported from the @Nestjs/GraphQL package) and the PubSub class in the GraphQL-Subscriptions package, which provides a simple publish/subscribe API.

The following subscription handler handles subscriptions to events by calling PubSub#asyncIterator. This method takes a single parameter triggerName, which corresponds to an event subject name.

const pubSub = new PubSub();

@Resolver((of) = > Author)
export class AuthorResolver {
  // ...
  @Subscription((returns) = > Comment)
  commentAdded() {
    return pubSub.asyncIterator('commentAdded'); }}Copy the code

All decorators are exported from the @nestjs/ GraphQL package, while the PubSub class is exported from the GraphQL-Subscriptions package.

This causes the following GraphQL schema to be generated in SDL: