Setting Up GraphQL with Code-First and Schema-First Approaches in NestJS

GraphQL is a query language for APIs that allows clients to request exactly the data they need, making it an efficient alternative to RESTful APIs. NestJS provides seamless integration with GraphQL, supporting two main approaches to defining GraphQL schemas: Code-First and Schema-First.

In this module, we will explore both approaches, learn how to set up GraphQL in a NestJS application, and compare the pros and cons of each method.


Table of Contents

  1. What is GraphQL?
  2. Setting Up GraphQL in NestJS
  3. Code-First Approach
  4. Schema-First Approach
  5. Comparing Code-First vs. Schema-First
  6. Best Practices
  7. Conclusion

What is GraphQL?

GraphQL is a query language and runtime for executing queries against your data. Unlike traditional REST APIs, which return predefined data from multiple endpoints, GraphQL allows you to query only the data you need, which reduces over-fetching and under-fetching.

Key features of GraphQL include:

  • Strongly typed schema: It defines the types of data and how they can be queried.
  • Single endpoint: Unlike REST APIs, which use multiple endpoints, GraphQL uses one endpoint for all requests.
  • Real-time updates: With subscriptions, GraphQL can provide real-time updates.

Setting Up GraphQL in NestJS

To use GraphQL in a NestJS application, you’ll need to install the necessary dependencies and configure the module. Here’s how to get started:

Step 1: Install Dependencies

bashCopyEditnpm install @nestjs/graphql graphql-tools graphql apollo-server-express

Step 2: Configure GraphQL in NestJS

In your main module (typically app.module.ts), configure the GraphQL module.

tsCopyEditimport { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: true,  // auto-generate the schema file
      playground: true,  // enable GraphQL playground for testing queries
    }),
    UserModule,
  ],
})
export class AppModule {}

This setup enables automatic schema generation and the GraphQL playground for interactive query testing.


Code-First Approach

The Code-First approach in GraphQL means that the schema is generated directly from TypeScript code using decorators. This approach leverages NestJS’s powerful decorators and makes it easy to define and maintain the schema directly in your code.

Installing Dependencies

To use the Code-First approach, you will need the following dependencies (which we’ve already installed):

  • @nestjs/graphql: The NestJS wrapper around GraphQL.
  • graphql: The GraphQL library for Node.js.
  • @nestjs/apollo-server-express: Apollo server integration for NestJS.

Creating the GraphQL Schema

In the Code-First approach, you define GraphQL types using decorators provided by @nestjs/graphql.

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;
}

Here, the @ObjectType decorator defines a GraphQL object type, and the @Field decorator specifies the fields of that type.

Resolvers and Types

Resolvers in the Code-First approach are used to fetch data for a specific query or mutation.

tsCopyEdit// user.resolver.ts
import { Resolver, Query, 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) {}

  @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> {
    return this.userService.create(name, email);
  }
}

In this example:

  • The @Query decorator defines a GraphQL query.
  • The @Mutation decorator defines a GraphQL mutation.

Schema-First Approach

The Schema-First approach means that the GraphQL schema is manually defined using the SDL (Schema Definition Language), and then NestJS resolvers are written to match the schema. This approach gives you more control over the schema but requires manually maintaining the schema and keeping it in sync with your resolvers.

Installing Dependencies

For the Schema-First approach, the dependencies remain the same as for Code-First, but the main difference lies in how you define the schema.

Defining the Schema

You define your schema in a .graphql file using the Schema Definition Language (SDL):

graphqlCopyEdit# schema.graphql
type User {
  id: Int!
  name: String!
  email: String!
}

type Query {
  getUser(id: Int!): User
}

type Mutation {
  createUser(name: String!, email: String!): User
}

Resolvers and Types

You then define resolvers to implement the schema.

tsCopyEdit// user.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './user.model';

@Resolver('User')
export class UserResolver {
  constructor(private userService: UserService) {}

  @Query('getUser')
  async getUser(@Args('id') id: number): Promise<User> {
    return this.userService.findOne(id);
  }

  @Mutation('createUser')
  async createUser(@Args('name') name: string, @Args('email') email: string): Promise<User> {
    return this.userService.create(name, email);
  }
}

Here, the @Resolver decorator refers to the User type defined in the SDL schema, and the resolvers are implemented in the same way as in the Code-First approach.


Comparing Code-First vs. Schema-First

FeatureCode-FirstSchema-First
Schema DefinitionAutomatically generated from codeDefined manually using SDL
FlexibilityEasier to maintain, less manual effortMore control over schema structure
Type SafetyStrongly typed via TypeScriptType checking requires additional setup
Developer ExperienceSeamless integration with NestJS decoratorsBetter suited for larger teams and existing schemas
Learning CurveEasier for beginners to understandMay require more upfront work to learn SDL

Best Practices

  • For small to medium projects: The Code-First approach is typically faster and more convenient, especially with NestJS’s powerful decorators.
  • For large projects or teams: The Schema-First approach offers more control over the schema and can be more suitable if you are working with external systems or need to maintain a consistent schema across multiple applications.
  • Keep resolvers simple: Avoid adding business logic in resolvers. Keep them focused on fetching and mutating data.
  • Type safety: Use the @nestjs/graphql decorators to enforce strict typing, ensuring better developer experience and fewer bugs.

Conclusion

NestJS makes it simple to integrate GraphQL into your application, whether you prefer the Code-First approach or the Schema-First approach. Both approaches are supported out of the box, and the choice depends on the complexity of your project and your team’s needs. Code-First is perfect for rapid development and small teams, while Schema-First offers more control for larger teams and complex systems.