Skip to main content

Building a GraphQL server with Nest and Apollo


Greetings!
GraphQL is rapidly gaining popularity as a more elegant solution for querying data compared to REST API. At the same time, NestJS streamlines the process of building efficient Node.js applications. In this article, I'll utilize two of them to construct a GraphQL server using NestJS.
Note: I'll assume you have a basic understanding of GraphQL and NestJS.

Source code - Nest GraphQL

GraphQL: A Quick Introduction

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
  • Schema: The GraphQL schema acts as a blueprint defining the data structure. It serves as the contract that establishes communication between the client and the server.
  • Data Sources: These are the real data stores, which can be diverse, including databases, REST APIs, and more.
  • Resolvers: Resolvers bridge the gap between the schema and data sources. They serve as the guides that transform GraphQL operations into tangible data.
  • Query - Retrieve data
  • Mutation - Modify data by performing operations like create, update, or delete
  • Subscription - Request real-time data updates

Nest

Nest is a highly efficient framework for Node.js application development, and it seamlessly incorporates GraphQL support. It offers two flavors: schema-first and code-first. In this article, we'll be focusing on the code-first approach because it's developer-friendly and doesn't require an additional learning curve for GraphQL syntax.

Let's build

We are building a straightforward book library where authors have multiple books and a book has an author to demonstrate relationships as well. Hence we have two modules for books and authors. We start with authors and then focus on books to understand relationships.

Initial setup

Nest comes equipped with a robust CLI that can generate project skeletons within seconds. Although the 'res' command can create a substantial portion of the codebase, in this case, I've opted for a manual approach to build everything from the ground up.
nest new nest-graphql-lms
cd nest-graphql-lms
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql
nest g m authors
nest g resolver authors
nest g service authors
nest g m books
nest g resolver books
nest g service books
Make sure to remove the generated service and controller as we don't need those files.

Configure GraphQL

The first thing we need to do is to configure the GraphQL module. Nest can generate the schema in memory however we chose to write it into a file.
@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/graphql-schema.gql'),
    }),
    BooksModule,
    AuthorsModule
  ],
})
export class AppModule {}

Define the entity

I'd like to define our author as follows. This will be the structure that the client will use for their queries.
type Author {
  id: String!
  firstName: String!
  lastName: String!
}
However, as we are utilizing code code-first approach, we don't have to create the schema manually. Instead, we let Nest generate a schema from the code. How do we do that? We use ObjectType() decorator to decorate the class and @Field decorator to decorate fields.
import { ObjectType, Field } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field()
  id: string;
  @Field()
  firstName: string;
  @Field()
  lastName: string;
}
As you can see, it is a normal Typescript class decorated with Nest's GraphQL decorators.

Define inputs

Unlike REST, GraphQL follows a different pattern where we use distinct objects for inputs and outputs, necessitating the use of separate input types. Input types look exactly the same as object types but with keyword input instead of type.
input CreateAuthorInput {
  firstName: String!
  lastName: String!
}
To generate this in Nest, we use @InputType() decorator.
Most of the time, an update will have the same fields as the create which introduces code duplications. Instead of adding all the fields, we can extend the create class using the PartialType.
import { InputType, Field, PartialType } from '@nestjs/graphql';

@InputType()
export class CreateAuthorInput {
  @Field()
  firstName: string;
  @Field()
  lastName: string;
}
@InputType()
export class UpdateAuthorInput extends PartialType(CreateAuthorInput) {
  @Field()
  id: string;
}

Define the service

We usually write business logic in the service layer. Using the same pattern, I'm adding author service to include business logic. However, I'm not adding a repository layer as our focus here is GraphQL. Instead, I'm using an array to hold authors. No explanation is needed as this is a typical service.
import { Injectable } from '@nestjs/common';
import { CreateAuthorInput, UpdateAuthorInput } from './author.input';
import { Author } from './author.entity';

@Injectable()
export class AuthorsService {
  private authors: Author[] = [
    { id: '100', firstName: 'Manjula', lastName: 'Jayawardana' },
    { id: '101', firstName: 'Manjula2', lastName: 'Jayawardana2' },
  ];

  async create(createAuthorInput: CreateAuthorInput): Promise<Author> {
    let { firstName, lastName } = createAuthorInput;
    const id = new Date().getTime() + '';
    let author = { id, firstName, lastName };
    this.authors.push(author);
    return author;
  }

  findAll() {
    return this.authors;
  }

  findOne(id: string) {
    return this.authors.find((author) => author.id === id);
  }

  update(id: string, updateAuthorInput: UpdateAuthorInput) {
    const author = this.findOne(updateAuthorInput.id);
    if (author) {
      author.firstName = updateAuthorInput.firstName;
      author.lastName = updateAuthorInput.lastName;
    }
    return author;
  }

  remove(id: string) {
    this.authors = this.authors.filter((author) => author.id !== id);
  }
}

Define the resolver

Everything is in place now. The last bit is to connect the schema with data using a resolver. A resolver is a class that is decorated with a @Resolver decorator. It accepts a function to denote the return entity. Nest effectively converts a regular class into a resolver using decorators.

Query

To define a query, we use the @Query decorator on a regular function. We can define the return value and an optional name for our query. Any arguments in the query can be collected using the @Args decorator. If you have used a framework like Spring before, this is very much similar to that.
  @Query(() => Author, { name: 'getAuthor' })
  findOne(@Args('id') id: string): Author {
    return this.authorsService.findOne(id);
  }

Mutation

To define a mutation, we use @Mutation decorator on a regular function. All the post, put, and delete operations in the REST world are handled through a mutation in GraphQL.
  @Mutation(() => Author)
  async createAuthor(@Args('createAuthorInput') createAuthorInput: CreateAuthorInput): Promise<Author> {
    return this.authorsService.create(createAuthorInput);
  }
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { AuthorsService } from './authors.service';
import { Author } from './author.entity';
import { CreateAuthorInput, UpdateAuthorInput } from './author.input';

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

  constructor(private readonly authorsService: AuthorsService) {}

  @Query(() => [Author], { name: 'getAuthors' })
  async findAll(): Promise<Author[]> {
    return this.authorsService.findAll();
  }

  @Query(() => Author, { name: 'getAuthor' })
  async findOne(@Args('id') id: string): Promise<Author> {
    return this.authorsService.findOne(id);
  }

  @Mutation(() => Author)
  async createAuthor(@Args('createAuthorInput') createAuthorInput: CreateAuthorInput): Promise<Author> {
    return this.authorsService.create(createAuthorInput);
  }

  @Mutation(() => Author)
  updateAuthor(@Args('updateAuthorInput') updateAuthorInput: UpdateAuthorInput) {
    return this.authorsService.update(updateAuthorInput.id, updateAuthorInput);
  }

  @Mutation(() => Author)
  removeAuthor(@Args('id') id: string): void {
    this.authorsService.remove(id);
  }
}

Start the application

We can start the server using the start command as usual. Our graph server will be served in the provided port - http://localhost:3000/graphql. Go and execute a few queries to see the result.

Resolving relationships

Up to this point, we didn't encounter any complexity other than learning new Nest decorators for GraphQL. As a client, I would like to query a book with its author. How can we achieve that?
type Book {
  id: String!
  title: String!
  price: Int!
  description: String
  author: Author
}

query book {
  getBook(id: "1") {
    id
    title
    price
    author {
      id
      firstName
      lastName
    }
  }
}
Note that the codes for the book are similar to the author. Hence I'm omitting repetitive codes and including only the important parts.
import { Field, HideField, Int, ObjectType } from "@nestjs/graphql";
import { Author } from "src/authors/author.entity";

@ObjectType()
export class Book {
  @Field()
  id: string;
  @Field({ nullable: true })
  author?: Author;
  @HideField()
  authorId?: string;
}
To query the author, we add an author field with the type Author decorated with @Field decorator. Other than that, to hide the authorId from the schema, we use the @HideField decorator in authorId.
In the GraphQL world, if we are to resolve a field, we need to provide a function with the same name as the field name.
Since the field name is "author" in our example, we need to create a function "author" in BookResolver to resolve the author for a book. Note the usage of @Parent() decorator that we use to access the properties parent object, Book in our example.
  @ResolveField()
  author(@Parent() book: Book): Author {
    const { authorId } = book;
    return this.authorService.findOne(authorId);
  }
If you run this code, it will not work. Why? Nest is a modular framework where modules are isolated. We need to export the AuthorService from the Author module and import the Author module into the Book module.
@Module({
  providers: [AuthorsResolver, AuthorsService],
  exports: [AuthorsService,]
})
export class AuthorsModule {}
@Module({
  imports: [AuthorsModule],
  providers: [BooksResolver, BooksService]
})
export class BooksModule {}
Run the application and see the result. I believe I have covered all the important topics. Hence, please refer to the GitHub repo for the complete code.

Conclusion

In this article, we have learned how to build a GraphQL API with Nest. Nest is a progressive web framework for building NodeJS applications and GraphQL is an effective way to query data. Use the official Nest documentation for further explanation.

Happy coding guys ☺

References


Comments