NestJS GraphQL: Code First Approach
Tags: nestjs, graphql, webdev, javascript
Published: 2024-03-27 00:00:03

In the previous article, we learned about the [Schema First Approach](https://dev.to/codexam/graphql-in-nestjs-a-concise-5-minute-guide-4ima) for setting up GraphQL with NestJS. Now, we will explore the Code First Approach.

In the Code First Approach, we define our GraphQL types and resolvers directly in TypeScript code instead of writing separate GraphQL schema files. This approach leverages the power of TypeScript and its type system to ensure consistency and type safety throughout our application.
## Repo Link : https://github.com/Subham-Maity/scalable_server_architecture/tree/main/example/2.%20graphql_typeorm/code-first-approach

## Step 1: Remove the GraphQL Schema File

First, let's remove the `book.schema.graphql` file that we created in the previous approach, as we won't be needing it anymore.

## Step 2: Modify the `app.module.ts`

Next, we need to update the `app.module.ts` file to configure the Code First Approach. Here's the updated code:

```typescript
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { BookResolver } from './book/book.resolver';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      playground: true, // Set to false in production
      autoSchemaFile: join(process.cwd(), 'src/schema.graphql'), // Automatically generate the GraphQL schema
      // ts-morph
      definitions: {
        path: join(process.cwd(), 'src/graphql.ts'), // Path to the generated file with TypeScript definitions
      },
    }),
    BookResolver,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
```

In this updated code, we've added two new properties to the `GraphQLModule.forRoot` configuration:

- `autoSchemaFile`: This property specifies the path where the automatically generated GraphQL schema file will be saved. In this case, it's set to `src/schema.graphql`.
- `definitions`: This property is specific to the `ts-morph` library, which is used to generate TypeScript definitions from the GraphQL schema. The `path` property inside `definitions` specifies the location where the TypeScript definitions will be saved. In this case, it's set to `src/graphql.ts`.

## Step 3: Install `ts-morph`

To use the Code First Approach, we need to install the `ts-morph` library, which is responsible for generating TypeScript definitions from the GraphQL schema. Run the following command to install `ts-morph`:

```bash
npm install ts-morph
```

## Step 4: Define the GraphQL Types and Resolvers

Now, let's create the `book.resolver.ts` file in the `book` folder, where we'll define our GraphQL types and resolvers.

```typescript
import { Query, Resolver } from '@nestjs/graphql';
import { Book } from './book.schema';
import { Book as BookModel } from '../graphql';

@Resolver(() => Book)
export class BookResolver {
  @Query(() => [Book], { name: 'books' }) // name set instead of getAllBooks it's now books
  getAllBooks() {
    const arr: BookModel[] = [
      {
        id: 1,
        title: 'Book 1',
        price: 10,
      },
      {
        id: 2,
        title: 'Book 2',
        price: 20,
      },
      {
        id: 3,
        title: 'Book 3',
        price: 30,
      },
    ];
    return arr;
  }
}
```

In this file, we've defined a `Book` type and a `books` query resolver using the `@Resolver` and `@Query` decorators from `@nestjs/graphql`.

The `Book` type is imported from the `book.schema.ts` file, which we'll create shortly. The `BookModel` type is imported from the `graphql.ts` file, which will be automatically generated by the `ts-morph` library based on our GraphQL schema.

## Step 5: Define the GraphQL Object Types

Next, we need to create the `book.schema.ts` file in the `book` folder, where we'll define our GraphQL object types.

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

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

  @Field()
  title: string;

  @Field(() => Int)
  price: number;
}
```

In this file, we've defined the `Book` object type using the `@ObjectType` decorator from `@nestjs/graphql`. Each field of the `Book` type is defined using the `@Field` decorator, along with the appropriate type decorator (`ID` for the `id` field, and `Int` for the `price` field).

## Step 6: Start the NestJS Server

Now, we can start the NestJS server by running the following command:

```bash
npm run start:dev
```

or

```bash
yarn start:dev
```

When you start the server, it will automatically generate the `schema.graphql` file in the `src` directory and the `graphql.ts` file with the TypeScript definitions. The `graphql.ts` file can be imported and used in other parts of your application to ensure type safety when working with GraphQL types.

You can then navigate to `http://localhost:3000/graphql` and access the GraphQL Playground to test your queries and mutations.

In this blog post, we learned how to set up GraphQL with NestJS using the Code First Approach. This approach allows us to define our GraphQL types and resolvers directly in TypeScript code, leveraging the power of TypeScript's type system for better type safety and consistency.

The Code First Approach has a few advantages over the Schema First Approach:

1. **Type Safety**: By defining our types in TypeScript, we get better type safety and can catch errors during development time, rather than at runtime.
2. **Colocation**: With the Code First Approach, we can colocate our types, resolvers, and other related code, making it easier to maintain and understand the codebase.
3. **IDE Support**: Most modern IDEs provide better tooling and code completion for TypeScript code, making it easier to work with GraphQL types and resolvers.

However, the Schema First Approach can still be preferred in certain scenarios, such as when working with an existing GraphQL API or when the schema is more stable and needs to be shared across different platforms or languages.

Overall, both approaches have their advantages and trade-offs, and the choice between them often depends on the specific requirements and preferences of the project and team.