Before we deep dive into integrating all three into a single project. And to take advantage of GraphQL query language, and Typeorm relational database with PostgreSQL (PSQL) or MySQL or any other DB. Let’s understand the individual pieces separately.

I assume that you have basic knowledge of ORMs, Express, Node, Graphql, NestJs. In case you are missing not, here is the brief introduction

GraphQL

GraphQL is a query language for the API. When a request (query in GraphQL world) triggers, It decides the data flows over the network.

Graphql trigger requests using a smart single endpoint unlike in traditional REST API. Where an endpoint is triggered according to the data and resource.

NestJS

NestJs is a framework used to serve our server needs. It uses Express and Fastify under the hood and has robust support for TypeScript. Which is designed and employed to make the backend structured that is in easy to maintain modules.

TypeORM

TypeORM is an Object Relational Mapping Tool that can be used with DataBase like Postgres, SQL, Mongo-DB. It supports multiple databases in the application and writing code in the modern JavaScript for our DataBase needs.

Lets us start building a basic author-books-genres program using TypeORM, NestJS, Graphql, RestAPI, Dataloader

1. Installing the NestJS and creating a scaffold project.

Either scaffold the project with the Nest CLI or clone a starter project (both will produce the same outcome).


npm i -g @nestjs/cli

nest new user-typeorm-graphql-dataloader

You can either choose yarn or npm, we are going with yarn route.

Install the dependencies for GraphQL, dataloader, typeorm, SQLite.

Once you have installed the new project, change your directory to the project we created and installed the following dependencies


yarn add dataloader graphql graphql-tools type-graphql typeorm graphql apollo-server-express voyager @types/graphql @nestjs/graphql sqlite3 @nestjs/typeorm

Create a .env file, where we will be putting our environment constants. For now, we will be using this file to populate the typeorm configuration and port where to run our servers. We are using SQLite for this tutorial purpose, but you can use any SQL database, typeorm supports multiple drivers

# .env 

TYPEORM_CONNECTION = sqlite
TYPEORM_DATABASE = data/dev.db
TYPEORM_LOGGING = true
TYPEORM_ENTITIES = src/db/models/*.entity.ts
TYPEORM_MIGRATIONS = src/db/migrations/*.ts
TYPEORM_MIGRATIONS_RUN = src/db/migrations
TYPEORM_ENTITIES_DIR = src/db/models
TYPEORM_MIGRATIONS_DIR = src/db/migrations
EXPRESS_PORT = 3000

Next, we need to create some directories, where our typeorm entities, typeorm migrations, and SQLite database are gonna resides


mkdir -p src/db/models  # our entites here
mkdir -p src/db/migrations # our migrations here
mkdir -p data # here our sqlite.db

Creating our Migrations

We gonna use typeorm CLI to generate our migrations, luckily typeorm migrations CLI come intact with typeorm package

  1. Author: We gonna create Author here
  2. Book: We gonna create Book here
  3. Genres: We gonna create Genres here
  4. BookGeneres: We gonna create Many-Many mapping between books and genres

That’s all the migration we need, below is the command to create typeorm migration since .env file already knows where to create migrations that we have already provided above.


 ts-node ./node_modules/typeorm/cli.js migration:create -n CreateAuthor    
 ts-node ./node_modules/typeorm/cli.js migration:create -n CreateBook 
 ts-node ./node_modules/typeorm/cli.js migration:create -n CreateGenre   
 ts-node ./node_modules/typeorm/cli.js migration:create -n CreateBookGenre 

The above commands should create 4 files in your src/db/migrations directory, here is how a single migration would look like.

# src/db/migrations/1563360242539-CreateAuthor.ts 

import {MigrationInterface, QueryRunner} from "typeorm";

export class CreateAuthor1563360242539 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<any> {
    }

    public async down(queryRunner: QueryRunner): Promise<any> {
    }

}

Typeorm uses epoch time as the prefix for migrations to run migrations in order. You can populate your migrations now with the creation of the table, here is how your migration should look like.


import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateAuthor1563360242539 implements MigrationInterface {

    private authorTable = new Table({
        name: 'authors',
        columns: [
            {
                name: 'id',
                type: 'INTEGER',
                isPrimary: true,
                isGenerated: true,
                generationStrategy: 'increment',
            },
            {
                name: 'name',
                type: 'varchar',
                length: '255',
                isNullable: false,
            },
            {
                name: 'created_at',
                type: 'timestamptz',
                isNullable: false,
                default: 'now()',
            },
            {
                name: 'updated_at',
                type: 'timestamptz',
                isNullable: false,
                default: 'now()',
            }],
    });

    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.createTable(this.authorTable);
    }

    public async down(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.dropTable(this.authorTable);
    }

}


import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';

export class CreateBook1563360267250 implements MigrationInterface {

    private bookTable = new Table({
        name: 'books',
        columns: [
            {
                name: 'id',
                type: 'INTEGER',
                isPrimary: true,
                isUnique: true,
                isGenerated: true,
                generationStrategy: 'increment',
            },
            {
                name: 'title',
                type: 'varchar',
                length: '255',
                isNullable: false,
            },
            {
                name: 'author_id',
                type: 'INTEGER',
                isNullable: false,
            },
            {
                name: 'created_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            },
            {
                name: 'updated_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            }],
    });

    private foreignKey = new TableForeignKey({
        columnNames: ['author_id'],
        referencedColumnNames: ['id'],
        onDelete: 'CASCADE',
        referencedTableName: 'authors',
    });

    public async up(queryRunner: QueryRunner): Promise<any> {
      await queryRunner.createTable(this.bookTable);
      await queryRunner.createForeignKey('books', this.foreignKey);
    }

    public async down(queryRunner: QueryRunner): Promise<any> {
      await queryRunner.dropTable(this.bookTable);
    }

}


import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateGenre1563360272082 implements MigrationInterface {

    private genreTable = new Table({
        name: 'genres',
        columns: [
            {
                name: 'id',
                type: 'INTEGER',
                isPrimary: true,
                isUnique: true,
                isGenerated: true,
                generationStrategy: 'increment',
            },
            {
                name: 'genre_name',
                type: 'varchar',
                length: '255',
                isNullable: false,
            },
            {
                name: 'created_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            },
            {
                name: 'updated_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            }],
    });


    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.createTable(this.genreTable);
    }

    public async down(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.dropTable(this.genreTable);
    }

}


import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateBookGenre1563360276498 implements MigrationInterface {

    private genreBookTable = new Table({
        name: 'books_genres',
        columns: [
            {
                name: 'id',
                type: 'INTEGER',
                isPrimary: true,
                isUnique: true,
                isGenerated: true,
                generationStrategy: 'increment',
            },
            {
                name: 'book_id',
                type: 'INTEGER',
                isNullable: true,
            },
            {
                name: 'genre_id',
                type: 'INTEGER',
                isNullable: true,
            },
            {
                name: 'created_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            },
            {
                name: 'updated_at',
                type: 'timestamptz',
                isPrimary: false,
                isNullable: false,
                default: 'now()',
            }],
    });

    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.createTable(this.genreBookTable);
    }

    public async down(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.dropTable(this.genreBookTable);
    }

}

Once migrations are setup, you need to run Typeorm migrations now, Typeorm provides a very easy to use CLI to run all the migrations, it will create a migrations table in the database, where it will keep a record of all the migrations it has applied.


ts-node ./node_modules/typeorm/cli.js migration:run

Once you execute this command dev.db file in created in src/data folder, Use SQLite browser to view the database, it must have all the tables you created and also the migration table

Creating our Model entities to map our database tables.

Let’s create Entity now, we are going to use typeorm data mapper method for our models, However, if you want to use typeorm active records, then you just need to extend all your Models from BaseEntity class which can be imported from like


import { BaseEntity } from 'typeorm';
class Auther extends BaseEntity
......
# src/db/models/author.entity.ts

import {
  Column,
  CreateDateColumn,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import Book from './book.entity';

@Entity()
export default class Author {

  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @CreateDateColumn({name: 'created_at'})
  createdAt: Date;

  @UpdateDateColumn({name: 'updated_at'})
  updatedAt: Date;

  // Associations
  @OneToMany(() => Book, book => book.authorConnection)
  bookConnection: Promise<Book[]>;

}


import {
  Entity,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
  Column, OneToMany,
  JoinColumn,
  ManyToOne,
} from 'typeorm';
import BookGenre from './book-genre.entity';
import Author from './author.entity';
import { Field, ObjectType } from 'type-graphql';

@ObjectType()
@Entity({name: 'books'})
export default class Book {

  @Field()
  @PrimaryGeneratedColumn()
  id: number;

  @Field()
  @Column()
  title: string;

  @Field()
  @Column({name: 'author_id'})
  authorId: number;

  @Field()
  @CreateDateColumn({name: 'created_at'})
  createdAt: Date;

  @Field()
  @UpdateDateColumn({name: 'updated_at'})
  updatedAt: Date;

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

  // Associations

  @ManyToOne(() => Author, author => author.bookConnection, {primary:
      true})
  @JoinColumn({name: 'author_id'})
  authorConnection: Promise<Author>;

  @OneToMany(() => BookGenre, bookGenre => bookGenre.genre)
  genreConnection: Promise<BookGenre[]>;
}


# src/db/models/genre.entity.ts

import {
  Entity,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  OneToMany, PrimaryGeneratedColumn,
} from 'typeorm';
import BookGenre from './book-genre.entity';

@Entity()
export default class Genre {

  @PrimaryGeneratedColumn()
  id: number;

  @Column({name: 'genre_name'})
  name: string;

  @CreateDateColumn({name: 'created_at'})
  createdAt: Date;

  @UpdateDateColumn({name: 'updated_at'})
  updatedAt: Date;

  // Associations
  @OneToMany(() => BookGenre, bookGenre => bookGenre.book)
  bookConnection: Promise<BookGenre[]>;
}
# src/db/models/book-genre.entity.ts


import {
  Entity,
  PrimaryColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  ManyToOne, JoinColumn, PrimaryGeneratedColumn,
} from 'typeorm';
import Genre from './genre.entity';
import Book from './book.entity';

@Entity()
export default class BookGenre {

  @PrimaryGeneratedColumn()
  id: number;

  @PrimaryColumn({name: 'book_id'})
  bookId: number;

  @PrimaryColumn({name: 'genre_id'})
  genreId: number;

  @CreateDateColumn({name: 'created_at'})
  createdAt: Date;

  @UpdateDateColumn({name: 'updated_at'})
  updatedAt: Date;

  // Associations
  @ManyToOne(() => Book, book => book.genreConnection, {primary:
      true})
  @JoinColumn({name: 'book_id'})
  book: Book[];

  @ManyToOne(() => Genre,  genre => genre.bookConnection, {primary:
      true})
  @JoinColumn({name: 'genre_id'})
  genre: Genre[];
}

That’s All. Once you have all the entities setup, you are ready to CRUD records in the database using repositories and map to the above entity models.

Read More:   3 Reasons to Bring Stateful Applications to Kubernetes – InApps Technology 2022

TypeORM Repositories as a global module to query the database

Since we took the data mapper route, we need to define repositories for each of our entities, so we are going to create a global service repo.service.ts which can be accessed across the nest application to query any table or entity. Before that, we have to configure TypeORM in the main module, which in our case is app.module.ts.

src/app.module.ts

@Module({

  imports: [
     TypeOrmModule.forRoot(), 
     RepoModule   // Don't worry, we will create this next
  ],  // to use typeorm
  controllers: [AppController],  
  providers: [AppService],
})
export class AppModule {}

Next create two files, repo.service.ts and repo.module.ts

# src/repo.service.ts

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import Author from './db/models/author.entity';
import Book from './db/models/book.entity';
import Genre from './db/models/genre.entity';
import BookGenre from './db/models/book-genre.entity';

@Injectable()
class RepoService {
  public constructor(
    @InjectRepository(Author) public readonly authorRepo: Repository<Author>,
    @InjectRepository(Book) public readonly bookRepo: Repository<Book>,
    @InjectRepository(Genre) public readonly genreRepo: Repository<Genre>,
    @InjectRepository(BookGenre) public readonly bookGenreRepo: Repository<BookGenre>,
  ) {}
}

export default RepoService;
# src/repo.module.ts

import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import RepoService from './repo.service';
import Author from './db/models/author.entity';
import Book from './db/models/book.entity';
import Genre from './db/models/genre.entity';
import BookGenre from './db/models/book-genre.entity';

@Global()
@Module({
  imports: [
    TypeOrmModule.forFeature([
      Author,
      Book,
      Genre,
      BookGenre,
    ]),
  ],
  providers: [RepoService],
  exports: [RepoService],
})
class RepoModule {

}
export default RepoModule;

Wow! We have just completed our setup for Typeorm, using data mapper and we have created migrations and models using typeorm one too many and typeorm many to many relationships.

Testing our first controller to see if we are able to query the database correctly

Open the app.service.ts file, we gonna use the existing function generated by nestjs CLI scaffolding to test our structure.

# src/app.service.ts

import { Injectable } from '@nestjs/common';
import RepoService from './repo.service';

@Injectable()
export class AppService {

  constructor(private readonly repoService: RepoService) {

  }

  async getHello(): Promise<string> { // querying database
    return `Total books are ${await this.repoService.bookRepo.count()}`;
  }
}
# src/app.controller.ts
......
  @Get()
  async getHello(): Promise<string> {
    return this.appService.getHello();
  }
.......

Once you have made changes to both app.service and app.controller file, you can run your project by ts-node src/main.ts. Once it runs, open the browser and you can see the following line

Total books are 0

Hurray! Your project is able to query the database. But that’s not all, We are going to employ the graphql integration into this project.

Integrating GraphQL with Nestjs and TypeORM

Since we already have installed the required packages above, in case you missed it, install the following package for graphql nestjs typeorm


yarn add type-graphql graphql dataloader @nestjs/graphql apollo-server-express
yarn add --dev @types/graphql 

Once the above packages are installed, we will modify our entities with type-graphql decorators, so the GraphQL types corresponding to entities are created inside our GraphQL schemas.

To create a type corresponding to the entity we will use @ObjectType() decorator from the type-graphql package. So you entities would look something like this

# src/db/models/author.entity.ts

......
@ObjectType()
@Entity({name: 'authors'})
export default class Author {

  @Field()
  @PrimaryGeneratedColumn()
  id: number;
.......

Once the type is exposed to the schema, we will start exposing our fields for the type using the @Field() decorator again from the type-graphql package.

Convert all your typeorm entities into GraphQL schemas.

Remember don’t annotate your associations with @Field(), we are going to deal with them separately.

Next, we have to import the GraphQL module in our app.module.ts so that NestJS will know about it. Change your app.module.ts file

......
import { GraphQLModule } from '@nestjs/graphql';

@Module({

  imports: [TypeOrmModule.forRoot(),
     RepoModule,
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      playground: true,
    }),
  ],
........

GraphQL will generate the schema at the schema.gql file, in your root directory. You don’t have to worry about this file as this is maintained and updated by NestJS graphql as per the Schema, resolvers, and mutations.

Adding GraphQL TypeORM Resolvers which includes our mutations and queries.

Create a new directory inside src where resolvers for all the typeorm entities would be there


mkdir -p src/resolvers

In our resolvers directory, we will create our first resolver author.resolver.ts for the author entity.

In our resolver directory, we will create a directory to hold our graphql input types.


mkdir -p src/resolvers/input

Since we are working on the author resolver first. We will begin by creating the author.input.ts which will hold the input types for the author entity.

The first input type will be the one required for our create author mutation responsible for creating a new author record in our database.


#src/resolvers/input/author.input.ts

import { Field, InputType } from 'type-graphql';

@InputType()
class AuthorInput {
  @Field()
  readonly name: string;
}

export default AuthorInput;

As we have set up input for our resolver, we will start with our first resolver. Each resolver class is annotated with @Resolver decorator which is imported from the nestjs-graphql package (@nestjs/graphql).


#src/resolvers/author.resolver.ts

import { Resolver } from '@nestjs/graphql';

@Resolver()
class AuthorResolver {
}

After we have created the class for the resolver, we will add the repo service as a dependency in the constructor. As our RepoService is Global, so no need to worry about providing this service at the module level.


#src/resolvers/author.resolver.ts

import { Resolver } from '@nestjs/graphql';

@Resolver()
class AuthorResolver {
   constructor(private readonly repoService: RepoService) {}
}

To create a query or mutation the class field should be annotated with a @Query() or @Mutation resolver respectively again import from NestJS-GraphQL package (@nestjs/graphql).


#src/resolvers/author.resolver.ts

import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';

@Resolver()
class AuthorResolver {
   constructor(private readonly repoService: RepoService) {}

  @Query(() => [Author])
  public async authors(): Promise<Author[]> {
    return this.repoService.authorRepo.find();
  }
  @Query(() => Author, {nullable: true})
  public async author(@Args('id') id: number): Promise<Author> {
    return  this.repoService.authorRepo.findOne(id);
  }

  @Mutation(() => Author)
  public async createAuthor(@Args('data') input: AuthorInput): 
    Promise<Author> {
      const author = this.repoService.authorRepo.create({name: 
      input.name});
      return  this.repoService.authorRepo.save(author);
  }
}
export default AuthorResolver;

To create the above the mutations and queries in our GraphQL schema. We have to add all the resolvers to our main module which in this case is app.module.ts imports. To keep things simple we will create a separate array of resolvers, and use the ES6 spread operator to import it.

#src/app.module.ts

........
const graphQLImports = [
  AuthorResolver,
];

@Module({
  imports: [TypeOrmModule.forRoot(),
    RepoModule,
    ...graphQLImports,
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      playground: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

The above resolver was an easy part, as it did not involve any associations to deal with. Though at the database level, the associations are handled by the typeorm. Each associative property needs to be resolved for the GraphQL.

Read More:   Observable Makes Collaboration Easier for Data Visualization – InApps Technology 2022

Now we will begin writing out resolver for the Book entity. The input type for the Book will have two options. Either create the book for an existing author, by his id. Or create a new book and a new author for the same

  1. To connect to an existing author in the database by his ID
  2. To create a new author along with the book.

The input type for the book is shown below with the above functionality implemented.


#src/resolvers/input/book.input.ts

import { Field, InputType } from 'type-graphql';
import AuthorInput from './author.input';

@InputType()
class BookAuthorConnectInput {
  @Field()
  readonly id: number;
}

@InputType()
class BookAuthorInput {
  @Field({nullable: true})
  readonly connect: BookAuthorConnectInput;

  @Field({nullable: true})
  readonly create: AuthorInput;
}

@InputType()
class BookInput {
  @Field()
  readonly title: string;

  @Field()
  readonly author: BookAuthorInput;
}

export default BookInput;

According to the associations defined in our typeorm migrations and typeorm entities. Each book must have one author. And from our GraphQL playground while fetching records for books the author record for the book can also be fetched. To fetch the author record we need to resolve the property. And to do so we will use @ResolveProperty() decorator from the @nestjs/graphql package.

For using the @ResolveProperty() you must pass the entity to the @Resolve(), decorator.

The @ResolveProperty() will expect the property to resolve as a parameter. Or the corresponding function name should match the name of the property to resolve. And GraphQL parent objects to its corresponding function.


#src/resolver/book.resolver.ts
......
  @ResolveProperty()
  public async author(@Parent() parent): Promise<Author> {
    return this.repoService.authorRepo.findOne(parent.authorId);
  }
......

Now we will begin with the final block of our application the Genre resolver. Below is the input type for the Genre and GenreBook

#src/resolvers/input/genre.input.ts

import { Field, InputType } from 'type-graphql';

@InputType()
class GenreInput {
  @Field()
  readonly name: string;
}
export default GenreInput;


#src/resolvers/input/book-genre.input.ts
import { Field, InputType } from 'type-graphql';

@InputType()
class GenreBookInput {
  @Field()
  readonly genreId: number;
  @Field()
  readonly bookId: number;
}

export default GenreBookInput;

Now that is finished with the input types for the Genre and BookGenre, we will create resolver for the same.

#src/resolvers/genre.resolver.ts

import { Args, Mutation, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
import RepoService from '../repo.service';
import Author from '../db/models/author.entity';
import Book from '../db/models/book.entity';
import BookInput from './input/book.input';
import Genre from '../db/models/genre.entity';
import GenreInput from './input/genre.input';
import BookGenre from '../db/models/book-genre.entity';

@Resolver(Genre)
class GenreResolver {

  constructor(private readonly repoService: RepoService) {}
  @Query(() => [Genre])
  public async genres(): Promise<Genre[]> {
    return this.repoService.genreRepo.find();
  }
  @Query(() => Genre, {nullable: true})
  public async genre(@Args('id') id: number): Promise<Genre> {
    return this.repoService.genreRepo.findOne(id);
  }

  @Mutation(() => Genre)
  public async createGenre(@Args('data') input: GenreInput): Promise<Genre> {
    const genre = new Genre();
    genre.name = input.name;
    return this.repoService.genreRepo.save(genre);
  }

  @ResolveProperty()
  public async book(@Parent() parent): Promise<Book[]> {
    const bookGenres = await this.repoService.bookGenreRepo.find({where: 
    {genreId: parent.id}, relations: ['book']});
    const books: Book[] = [];
    bookGenres.forEach(async bookGenre => books.push(await 
      bookGenre.book));
    return books;
  }
}

export default GenreResolver;
#src/resovlers/book-genre.resolver.ts

import { Args, Mutation, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
import RepoService from '../repo.service';
import Author from '../db/models/author.entity';
import Book from '../db/models/book.entity';
import BookInput from './input/book.input';
import Genre from '../db/models/genre.entity';
import GenreInput from './input/genre.input';
import BookGenre from '../db/models/book-genre.entity';
import BookGenreInput from './input/book-genre.input';
import { Arg } from 'type-graphql';

@Resolver()
class BookGenreResolver {

  constructor(private readonly repoService: RepoService) {}
  @Mutation(() => BookGenre)
  public async createBookGenre(@Args('data') input: BookGenreInput): Promise<BookGenre> {
    const bookGenre = new BookGenre();
    const {bookId, genreId} = input;
    bookGenre.bookId = bookId;
    bookGenre.genreId = genreId;
    return this.repoService.bookGenreRepo.save(bookGenre);
  }

  @Query(() => [BookGenre])
  public async bookGenres(): Promise<BookGenre[]> {
    return this.repoService.bookGenreRepo.find();
  }

  @Query(() => BookGenre)
  public async bookGenre(@Arg('id') id: number): Promise<BookGenre> {
    return this.repoService.bookGenreRepo.findOne(id);
  }
}

export default BookGenreResolver;

The queries and mutations created can be run on the GraphQL playground, you can navigate to GraphQL playground on the route “/graphql”

GraphQL
The GraphQL playground.

The queries and mutations to run can be written on the left side of the graphql playground whose result is displayed on the right side after clicking the play button.

For your reference, below are some graphql queries and mutations with their results fetched from the playground.


// mutation to create an author
mutation {
  createAuthor(data: {
    name: "Sahil"
  }) {
    id
    name
    createdAt
    updatedAt
  }
}
Create Author GraphQL Mutation
Create Author GraphQL Mutation
Authors GraphQL Query
Authors GraphQL Query.

The problem in the current approach.

The problem is not an obvious problem i.e our code is not going to blow up everything is going to work, the problem is an intuitive problem, using the dataloader we can make our application much more efficient. To illustrate the current problem we will fetch the books record using our genres query.

In the above query whenever the book is fetched it triggers the @ResolveProperty() method and the book records are fetched. i.e for n Genres n database queries will run and a total of n+1 queries will be executed. The database connections are the most expensive task we have. To resolve this problem we will use dataloaders.

Introduction To Dataloader

The Dataloader is a generic utility developed by facebook used to abstract request batching and caching.

The dataloader will wait for a single event loop cycle before it executes. And it calls back function and by the time an event loop cycle is completed, all the Book ids for the Book records to be fetched will have arrived. And instead of running n queries for n Genres we will run a single query. Which is a huge improvement over the previous approach

To use dataloader we need the dataloader package. Which in case you haven’t installed already can be installed by the command mentioned below.


yarn add dataloader

Now that our package is installed we will make the directory where our packages will sit.


mkdir -p src/db/loaders

Now that our directory for the resolver is created. We will write our loader to fetch Books based on the Genre Ids passed.

#src/db/loaders/books.loader.ts

import DataLoader = require('dataloader');
import Book from '../models/book.entity';
import { getRepository } from 'typeorm';
import BookGenre from '../models/book-genre.entity';

const batchBooks = async (genreIds: string[]) => {
  const bookGenres = await getRepository(BookGenre)
    .createQueryBuilder('bookGenres')
    .leftJoinAndSelect('bookGenres.book', 'book')
    .where('bookGenres.id IN(:...ids)', {ids: genreIds})
    .getMany();
  const genreIdToBooks: {[key: string]: Book[]} = {};
  bookGenres.forEach(bookGenre => {
    if (!genreIdToBooks[bookGenre.genreId]) {
      genreIdToBooks[bookGenre.genreId] = [(bookGenre as any).__book__];
    } else {
      genreIdToBooks[bookGenre.genreId].push((bookGenre as any).__book__);
    }
  });
  return genreIds.map(genreId => genreIdToBooks[genreId]);
};
const genreBooksLoader = () => new DataLoader(batchBooks);

export {
  genreBooksLoader,
};

The loaders are passed to each of our queries and mutations as a part of the context. Therefore, we will now begin writing our GraphQL context type.


mkdir -p src/types/
#src/types/graphql.types.ts

import { genreBooksLoader } from '../db/loaders/books.loader';

export interface IGraphQLContext {
  genreBooksLoader: ReturnType<typeof genreBooksLoader>;
}

Once the types for the GraphQL context created, we will create a GraphQL context. The context gets created in our GraphQL module present in the main app.module.ts

#src/app.module.ts

......
@Module({

  imports: [TypeOrmModule.forRoot(),
    RepoModule,
    ...graphQLImports,
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      playground: true,
      context: {
        genreBooksLoader: genreBooksLoader(),
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
......

Now that the loader is available to each of our queries and mutation via context. We can use it and modify our book to resolve property in our Genre resolver.

#src/resolvers/genre.resolver.ts

........
  @ResolveProperty()
  public async book(@Parent() parent, @Context() {genreBooksLoader}: 
  IGraphQLContext): Promise<Book[]> {
    return genreBooksLoader.load(parent.id);
  }

........

The author’s property is now resolve using the dataloader function exposed in our GraphQL context. And we have resolved the n+1 problem instead of running n database queries. The records will now be fetched using a single query. Thanks to Dataloader.

Read More:   How Devs Can Help Beat the COVID-19 Pandemic – InApps 2022

Also Learn TypeORM With NEST JS Basic Tutorial

Source: InApps.net

List of Keywords users find our article on Google:

typeorm
nestjs
graphql jobs
fastify
nest.js
ts-node
apollo graphql
typescript playground
type graphql
apollo resolver
dataloader
typescript find
typescript jobs
voyager recruitment
nestjs tutorial
entity framework tutorial
typescript books
nest js
graphql resolver
graphql recruitment
graphql typescript
typescript export
apollo servers
custom application development
nestjs typeorm
typescript recruitment
nestjs repository
nestjs testing
graphql npm
nestjs integration test
dataloader.io
apollo-server-express
typescript foreach
entity framework many to many
typescript express
entity framework many to many database first
apollo graphql tutorial
typeorm migration generate
mongodb populate
apollo server tutorial
apollo mutation
foreach typescript
graphql types
typescript promise
typescript constructor
apollo server mutation
graphql book
express typescript
genrenew
nestjs typeorm graphql
typeorm queryrunner
nestjs npm
nestjs typeorm migrations
typeorm npm
typeorm one to one
type orm
typeorm create
npm apollo-client
nest cli
migrations nestjs
npm apollo server
graphql net core
nest js jobs
typescript date
net graphql
timestamptz
mongoclient connect promise
typescript export class
what is nestjs
custom vision api tutorial
ts playground typescript
entity framework 101
express vs nest js
typescript database
typescript return type
graphql object type
string to date typescript
apollo server
express controller typescript
graphql apollo tutorial
entity framework core tutorial
graphql postgres
db scaffold
mongo populate
apollo server postgres
graphql tools
order graphql
apollo linkedin extension
apollo server typescript
typescript nullable
graphql postgresql
graphql voyager
voyager graphql
graphql resolve
postgresql saas
typescript copy object
graphql-core
dataloader nestjs
nestjs mongodb graphql
nestjs graphql typeorm
typeorm graphql nestjs
nestjs typeorm sqlite
typeorm playground
typeorm tutorial
nestjs/typeorm migrations
nestjs vs fastify
typeorm cli
typeorm types
playground ts
typeorm nestjs
typeorm migrations
ondelete(‘cascade’)
addall books
npm graphql
typeorm many to one
typeorm get entity with relations
typeorm migration
findone order by typeorm
typeorm find by ids
where typeorm
@nestjs/typeorm
typeorm many to many
order by typeorm
typeorm update entity
find where typeorm
game ui database
nestjs find
nestjs typescript starter
dataloader object
typeorm nullable column
graphql package
nestjs pg
npm graphql-tools
typeorm findone not found
apollo server wow
npm sqlite3
create api nestjs
nestjs/cli
typeorm date column
entities nestjs
typeorm database types
graphql youtube
promise mysql
typeorm custom repository
typeorm multiple databases
пегас туристик оператор
typegraphql
apollo use mutation
fastify cli
nestjs app
apollo client mutation
nestjs typescript
apollo server testing
express apollo server
find one typeorm
nestjs database
net core graphql
mongodb playground
apollo mutation update
fastify route
typescript graphql
wow default ui
apollo generate typescript
apollo server express
apollo wow server
mysql.data.entity
typescript return this
“nes technologies”
graphql with typescript
nest js testing
nestjs module
wow db
created_at mongodb
postgresql return
apollo graphql mutation
dataloader partner
express vs nestjs
nestjs disadvantages
promise in typescript
promise typescript
types in graphql
types/graphql
apollo-server-express playground
entity framework migrations tutorial
graphql basic types
psql trigger
apollo client typescript
find typescript
findone mongodb
graphql express typescript
javascript foreach async
typeorm and
api nestjs
graphql client typescript
nestjs controller async
net graphql client
typeorm entity unique
express for typescript
graphql db
import dataloader
nestjs controller
typeorm generate migration from entity
typeorm migration api
typescript apollo server
apollo mutations
apolo graphql
graphql function
graphql net
nestjs create new project
nestjs migration
scaffold database entity framework core
sqlite net
the appserv open project
trigger postgresql
typescript graphql client
typescript with mongodb
apollo graph ql
appollo graphql
graphql implements
mysql foreach
nestjs graphql tutorial
object-mapper npm
ts async constructor
apollo dev tools
apollo graphql typescript
apollo query variables
apollo-server typescript
express graphql typescript
graphql next
typescript extend type
apollo graphql resolver
apollo resolvers
apollo-graphql
db imports
entity framework graphql
generate schema graphql
graphql relationship
integer typescript
mongo findone
postgresql graphql
typeormmodule.forfeature
entity framework sqlite connection string
next graphql
npm sqlite
postgresql case
type script date
typescript design
entity framework code first sqlite
express typescript graphql
nestjs api rest
sqlite3 npm
typescript game
typescript requests
wp graphql
apollo server types
dataloader next
graphql map type
graphql save data
graphql schema to typescript
nestjs service constructor
sqlite length of string
this.$apollo.query
typescript apollo
typescript apollo client
apollo graphql tools
graphql mutate
graphql resolvers
joincolumn
mongodb typescript
nestjs express
nestjs one to one
nestjs ts-node
npm request promise
postgres populate table
resolvers graphql
schemas graphql
sqlite date
typeorm documentation
typeormmodule nestjs
typescript date type
typescript express next type
typescript nameof
array copy typescript
entity framework sqlite
findone javascript
graphql data types
graphql group
graphql mutation update object
graphql with entity framework
graphql-voyager
javascript fetch graphql
node graphql
postgres graphql
readonly typescript
record typescript
schema graphql
typescript .env
apollo graphql resolvers
apollo use query
graphql add data
graphql client mutation
graphql schema tutorial
module type package
postgresql trigger
resolver graphql
three js typescript
typescript db connection
typescript record
typescript string search
apollo client resolvers
foreach async
graphql mutation input
nestjs flow
npm mongo
typescript class
typescript express app type
typescript extends
typescript mapper
typescript starter node
apollo cli
date graphql
date() postgres
field resolver graphql
graphql nullable
graphql resolvers tutorial
graphql use mutation
graphql with fetch
psql current date
typescript date to string
only a type can be imported resolves to a package
postgres tutorial
associative property
graphql
orms
Rate this post

Let’s create the next big thing together!

Coming together is a beginning. Keeping together is progress. Working together is success.

Let’s talk

Get a custom Proposal

Please fill in your information and your need to get a suitable solution.

    You need to enter your email to download

      Success. Downloading...