Harnessing the power of TypeScript & GraphQL

GraphQL is a powerful query language for APIs and a runtime for fulfilling those queries with your existing data. It’s an elegant approach that solves many problems typically found with REST APIs. For background, we suggest reading this comparison between GraphQL and REST. GraphQL combined with TypeScript helps you develop better type safety with your GraphQL queries, giving you end-to-end typing.

In this chapter, we assume a basic understanding of GraphQL, and focus on how to work with the built-in @nestjs/graphql module. The GraphQLModule is a wrapper around the Apollo server. We use this proven GraphQL package to provide a way to use GraphQL with Nest.

Installation

Start by installing the required packages:

  1. $ npm i --save @nestjs/graphql graphql-tools graphql

Depending on what underlying platform you use (Express or Fastify), you must also install either apollo-server-express or apollo-server-fastify.

Overview

Nest offers two ways of building GraphQL applications, the code first and the schema first methods. You should choose the one that works best for you. Most of the chapters in this GraphQL section are divided into two main parts: one you should follow if you adopt code first, and the other to be used if you adopt schema first.

In the code first approach, you use decorators and TypeScript classes to generate the corresponding GraphQL schema. This approach is useful if you prefer to work exclusively with TypeScript and avoid context switching between language syntaxes.

In the schema first approach, the source of truth is GraphQL SDL (Schema Definition Language) files. SDL is a language-agnostic way to share schema files between different platforms. Nest automatically generates your TypeScript definitions (using either classes or interfaces) based on the GraphQL schemas to reduce the need to write redundant boilerplate code.

Getting started with GraphQL & TypeScript

Once the packages are installed, we can import the GraphQLModule and configure it with the forRoot() static method.

  1. import { Module } from '@nestjs/common';
  2. import { GraphQLModule } from '@nestjs/graphql';
  3. @Module({
  4. imports: [
  5. GraphQLModule.forRoot({}),
  6. ],
  7. })
  8. export class AppModule {}

The forRoot() method takes an options object as an argument. These options are passed through to the underlying Apollo instance (read more about available settings here). For example, if you want to disable the playground and turn off debug mode, pass the following options:

  1. import { Module } from '@nestjs/common';
  2. import { GraphQLModule } from '@nestjs/graphql';
  3. @Module({
  4. imports: [
  5. GraphQLModule.forRoot({
  6. debug: false,
  7. playground: false,
  8. }),
  9. ],
  10. })
  11. export class AppModule {}

As mentioned, these options will be forwarded to the ApolloServer constructor.

Official enterprise support

  • Quick start - 图1 Providing technical guidance
  • Quick start - 图2 Performing in-depth code reviews
  • Quick start - 图3 Mentoring team members
  • Quick start - 图4 Advising best practices

Explore more

GraphQL playground

The playground is a graphical, interactive, in-browser GraphQL IDE, available by default on the same URL as the GraphQL server itself. To access the playground, you need a basic GraphQL server configured and running. To see it now, you can install and build the working example here. Alternatively, if you’re following along with these code samples, once you’ve complete the steps in the Resolvers chapter, you can access the playground.

With that in place, and with your application running in the background, you can then open your web browser and navigate to http://localhost:3000/graphql (host and port may vary depending on your configuration). You will then see the GraphQL playground, as shown below.

Quick start - 图5

Multiple endpoints

Another useful feature of the @nestjs/graphql module is the ability to serve multiple endpoints at once. This lets you decide which modules should be included in which endpoint. By default, GraphQL searches for resolvers throughout the whole app. To limit this scan to only a subset of modules, use the include property.

  1. GraphQLModule.forRoot({
  2. include: [CatsModule],
  3. }),

Code first

In the code first approach, you use decorators and TypeScript classes to generate the corresponding GraphQL schema.

To use the code first approach, start by adding the autoSchemaFile property to the options object:

  1. GraphQLModule.forRoot({
  2. autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
  3. }),

The autoSchemaFile property value is the path where your automatically generated schema will be created. Alternatively, the schema can be generated on-the-fly in memory. To enable this, set the autoSchemaFile property to true:

  1. GraphQLModule.forRoot({
  2. autoSchemaFile: true,
  3. }),

A fully working code first sample is available here.

Schema first

To use the schema first approach, start by adding a typePaths property to the options object. The typePaths property indicates where the GraphQLModule should look for GraphQL SDL schema definition files you’ll be writing. These files will be combined in memory; this allows you to split your schemas into several files and locate them near their resolvers.

  1. GraphQLModule.forRoot({
  2. typePaths: ['./**/*.graphql'],
  3. }),

You will typically also need to have TypeScript definitions (classes and interfaces) that correspond to the GraphQL SDL types. Creating the corresponding TypeScript definitions by hand is redundant and tedious. It leaves us without a single source of truth — each change made within SDL forces us to adjust TypeScript definitions as well. To address this, the @nestjs/graphql package can automatically generate TypeScript definitions from the abstract syntax tree (AST). To enable this feature, add the definitions options property when configuring the GraphQLModule.

  1. GraphQLModule.forRoot({
  2. typePaths: ['./**/*.graphql'],
  3. definitions: {
  4. path: join(process.cwd(), 'src/graphql.ts'),
  5. },
  6. }),

The path property of the definitions object indicates where to save generated TypeScript output. By default, all generated TypeScript types are created as interfaces. To generate classes instead, specify the outputAs property with a value of 'class'.

  1. GraphQLModule.forRoot({
  2. typePaths: ['./**/*.graphql'],
  3. definitions: {
  4. path: join(process.cwd(), 'src/graphql.ts'),
  5. outputAs: 'class',
  6. },
  7. }),

The above approach dynamically generates TypeScript definitions each time the application starts. Alternatively, it may be preferable to build a simple script to generate these on demand. For example, assume we create the following script as generate-typings.ts:

  1. import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
  2. import { join } from 'path';
  3. const definitionsFactory = new GraphQLDefinitionsFactory();
  4. definitionsFactory.generate({
  5. typePaths: ['./src/**/*.graphql'],
  6. path: join(process.cwd(), 'src/graphql.ts'),
  7. outputAs: 'class',
  8. });

Now you can run this script on demand:

  1. $ ts-node generate-typings

Hint You can compile the script beforehand (e.g., with tsc) and use node to execute it.

To enable watch mode for the script (to automatically generate typings whenever any .graphql file changes), pass the watch option to the generate() method.

  1. definitionsFactory.generate({
  2. typePaths: ['./src/**/*.graphql'],
  3. path: join(process.cwd(), 'src/graphql.ts'),
  4. outputAs: 'class',
  5. watch: true,
  6. });

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

  1. definitionsFactory.generate({
  2. // ...,
  3. emitTypenameField: true,
  4. });

To generate resolvers (queries, mutations, subscriptions) as plain fields without arguments, enable the skipResolverArgs option.

  1. definitionsFactory.generate({
  2. // ...,
  3. skipResolverArgs: true,
  4. });

A fully working schema first sample is available here.

Accessing generated schema

In some circumstances (for example end-to-end tests), you may want to get a reference to the generated schema object. In end-to-end tests, you can then run queries using the graphql object without using any HTTP listeners.

You can access the generated schema (in either the code first or schema first approach), using the GraphQLSchemaHost class:

  1. const { schema } = app.get(GraphQLSchemaHost);

Hint You must call the GraphQLSchemaHost#schema getter after the application has been initialized (after the onModuleInit hook has been triggered by either the app.listen() or app.init() method).

Async configuration

When you need to pass module options asynchronously instead of statically, use the forRootAsync() method. As with most dynamic modules, Nest provides several techniques to deal with async configuration.

One technique is to use a factory function:

  1. GraphQLModule.forRootAsync({
  2. useFactory: () => ({
  3. typePaths: ['./**/*.graphql'],
  4. }),
  5. }),

Like other factory providers, our factory function can be async and can inject dependencies through inject.

  1. GraphQLModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useFactory: async (configService: ConfigService) => ({
  4. typePaths: configService.getString('GRAPHQL_TYPE_PATHS'),
  5. }),
  6. inject: [ConfigService],
  7. }),

Alternatively, you can configure the GraphQLModule using a class instead of a factory, as shown below:

  1. GraphQLModule.forRootAsync({
  2. useClass: GqlConfigService,
  3. }),

The construction above instantiates GqlConfigService inside GraphQLModule, using it to create options object. Note that in this example, the GqlConfigService has to implement the GqlOptionsFactory interface, as shown below. The GraphQLModule will call the createGqlOptions() method on the instantiated object of the supplied class.

  1. @Injectable()
  2. class GqlConfigService implements GqlOptionsFactory {
  3. createGqlOptions(): GqlModuleOptions {
  4. return {
  5. typePaths: ['./**/*.graphql'],
  6. };
  7. }
  8. }

If you want to reuse an existing options provider instead of creating a private copy inside the GraphQLModule, use the useExisting syntax.

  1. GraphQLModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useExisting: ConfigService,
  4. }),