GraphQL with NestJS

Integrating GraphQL into a NestJS application using Apollo Server or other GraphQL libraries.


Setting up Apollo Server with NestJS

Explanation: Setting up Apollo Server with NestJS

This guide will walk you through integrating Apollo Server into a NestJS application to create a GraphQL API. NestJS provides a structured and modular way to build server-side applications, and Apollo Server handles the GraphQL layer.

The core idea is to leverage NestJS modules and dependency injection to manage your GraphQL schema, resolvers, and Apollo Server instance. We'll use the @nestjs/graphql package to simplify this integration.

Here's a high-level overview of the process:

  1. Install Dependencies: Install the necessary npm packages for Apollo Server and NestJS GraphQL integration.
  2. Define Schema: Create your GraphQL schema using either code-first or schema-first approaches.
  3. Implement Resolvers: Write resolvers that fetch and transform data to fulfill GraphQL queries.
  4. Configure Apollo Server: Configure Apollo Server within your NestJS application using the GraphQLModule.
  5. Integrate Playground (Optional): Enable the Apollo Server Playground for easy API exploration.

Detailed Guide: Installing and Configuring Apollo Server within a NestJS Application

Step 1: Install Dependencies

First, install the required packages using npm or yarn:

npm install @nestjs/graphql @nestjs/platform-express graphql apollo-server-express class-validator class-transformer 

Or with yarn:

yarn add @nestjs/graphql @nestjs/platform-express graphql apollo-server-express class-validator class-transformer 

Explanation:

  • @nestjs/graphql: The official NestJS GraphQL module that simplifies integration with Apollo Server.
  • @nestjs/platform-express: Provides the Express platform adapter for NestJS.
  • graphql: The core GraphQL library.
  • apollo-server-express: Allows you to use Apollo Server with Express.
  • class-validator & class-transformer: Needed for input validation in GraphQL resolvers (optional, but highly recommended).

Step 2: Define Schema (Code-First Approach - Recommended)

The code-first approach allows you to define your schema using TypeScript classes.

Create a file named src/entities/author.entity.ts:

import { ObjectType, Field, ID } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field(() => ID)
  id: string;

  @Field()
  firstName: string;

  @Field()
  lastName: string;
} 

Create a file named src/entities/book.entity.ts:

import { ObjectType, Field, ID } from '@nestjs/graphql';
import { Author } from './author.entity';

@ObjectType()
export class Book {
  @Field(() => ID)
  id: string;

  @Field()
  title: string;

  @Field(() => Author)
  author: Author;
} 

Step 3: Implement Resolvers

Create a resolver file named src/book/book.resolver.ts:

import { Resolver, Query, Args, Int, ResolveField, Parent } from '@nestjs/graphql';
import { Book } from '../entities/book.entity';
import { Author } from '../entities/author.entity';

@Resolver(() => Book)
export class BookResolver {

  private books: Book[] = [
    { id: '1', title: 'The Lord of the Rings', author: { id: '1', firstName: 'J.R.R', lastName: 'Tolkien' } },
    { id: '2', title: 'The Hobbit', author: { id: '1', firstName: 'J.R.R', lastName: 'Tolkien' } },
    { id: '3', title: 'The Hitchhiker\'s Guide to the Galaxy', author: { id: '2', firstName: 'Douglas', lastName: 'Adams' } },
  ];

  private authors: Author[] = [
    { id: '1', firstName: 'J.R.R', lastName: 'Tolkien' },
    { id: '2', firstName: 'Douglas', lastName: 'Adams' },
  ];

  @Query(() => [Book])
  async books(): Promise {
    return this.books;
  }

  @Query(() => Book, { nullable: true })
  async book(@Args('id', { type: () => Int }) id: number): Promise {
    return this.books.find(book => book.id === id.toString());
  }

  @ResolveField(() => Author)
  async author(@Parent() book: Book): Promise {
    return this.authors.find(author => author.id === book.author.id);
  }
} 

Create a resolver file named src/author/author.resolver.ts:

import { Resolver, Query, Args, Int, ResolveField, Parent } from '@nestjs/graphql';
import { Author } from '../entities/author.entity';

@Resolver(() => Author)
export class AuthorResolver {

  private authors: Author[] = [
    { id: '1', firstName: 'J.R.R', lastName: 'Tolkien' },
    { id: '2', firstName: 'Douglas', lastName: 'Adams' },
  ];

  @Query(() => [Author])
  async authors(): Promise {
    return this.authors;
  }

  @Query(() => Author, { nullable: true })
  async author(@Args('id', { type: () => Int }) id: number): Promise {
    return this.authors.find(author => author.id === id.toString());
  }
} 

Explanation:

  • @Resolver(() => Book): Indicates that this class is a resolver for the Book type.
  • @Query(() => [Book]): Defines a GraphQL query named books that returns an array of Book objects.
  • @Query(() => Book, { nullable: true }): Defines a GraphQL query named book which takes an ID as an argument and returns a single Book object.
  • @ResolveField(() => Author): Defines a resolver for the author field within the Book type, allowing you to fetch the author related to a particular book.
  • @Args: Extracts arguments passed to the query.
  • @Parent(): In @ResolveField, @Parent() injects the parent object (in this case, a Book instance) into the resolver function, which allows you to access the parent's properties and use them to fetch related data.

Step 4: Configure Apollo Server with GraphQLModule

Modify your src/app.module.ts file to configure the GraphQLModule:

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { BookResolver } from './book/book.resolver';
import { AuthorResolver } from './author/author.resolver';

@Module({
  imports: [
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      autoSchemaFile: 'schema.gql', // Generates the schema file.
      playground: true, // Enable GraphQL Playground in development.
      debug: true, //Enable Debug
    }),
  ],
  providers: [BookResolver, AuthorResolver],
})
export class AppModule {} 

Explanation:

  • GraphQLModule.forRoot(): Configures the GraphQLModule with the provided options.
  • driver: ApolloDriver: Specifies to use Apollo Server as the GraphQL driver.
  • autoSchemaFile: 'schema.gql': Automatically generates the GraphQL schema file. This is crucial for the code-first approach. The file will be located in the root of your project after the application is run for the first time.
  • playground: true: Enables the Apollo Server Playground for easy testing. Access it in your browser (usually at /graphql).
  • debug: true: Enables debug to show error message

Step 5: Run the Application

Start your NestJS application:

npm run start:dev 

Open your browser and navigate to http://localhost:3000/graphql (or the port your application is running on) to access the Apollo Server Playground. You should see the GraphQL interface where you can explore your schema and execute queries.

Step 6: Create module for the resolvers (Optional but recomended)

Create new module named book to isolate the book and the authoe resolver

nest g module book

Update the new book.module.ts with the author and book resolver

import { Module } from '@nestjs/common';
import { BookResolver } from './book.resolver';
import { AuthorResolver } from 'src/author/author.resolver';

@Module({
  providers: [BookResolver,AuthorResolver]
})
export class BookModule {} 

Now Import the Book module in the app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { BookModule } from './book/book.module';

@Module({
  imports: [
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      autoSchemaFile: 'schema.gql', // Generates the schema file.
      playground: true, // Enable GraphQL Playground in development.
      debug: true, //Enable Debug
    }),
    BookModule,
  ],
  providers: [],
})
export class AppModule {}