Categories
Frontend Developers GraphQL Javascript Node React Developers React Native Developers React Native Web Remote developer Top Coder

4 Great Ways US-Based Company Can Hire Software Developer



Most software development companies in the US facing issues in the hiring of software developers. Due to the lack of local talented professionals.

Although the graph of technical graduates is in increasing order. Still, top-rank companies like google and apple with their high packages and perks find it challenging, hire well-skilled software developers.

It may be a difficult and expensive task to do. But, hire a top coder or developer is not something that is impossible.




First of all, before hiring a software developer there are some things you should keep in mind.


Every software developer who walks around for an interview comes with a different skillset. And every employer searches various skills and attitudes. There are various factors that fill help you to find the right software developer for you.


Analytical Thinking:-

As a software developer, a person should have an analytical approach. This gives you better thinking and observation power to understand things more deeply.

It makes you creative and productive. Before hiring a software developer, give it a sight to his/her analytical thinking.


Communication:-

If you are in one of who thinks communication skills are not important as a software developer. Then you are absolutely wrong. Most people think if a candidate has well technical skills then you can ignore his/her communication skills.


End of the day he/she is going to do coding designing. But what about sharing the idea? don’t forget development is teamwork. you have to share your idea with your respective team. And for them to understand you need to communicate with them well.


Coding:-

Obviously, coding is the key point if you going to hire a software developer. A software developer without the best coding skills is for no use.


As a software developer, we have to work on the best programming languages that are not user-friendly. But highly demand technologies like Node.js, react native, react native web, AngularJS. so a software developer should have extreme knowledge of coding.


Attitude:-

“Skill can learn not the attitude”. A positive attitude leads towards a positive end. If an employee with a negative attitude will impact on other employees.

Because of that indirectly the work going to suffer. So a software developer having a positive attitude toward their work is important.


Work ethics:-

Work ethics are very important for any employee. If an employee doesn’t have any work ethics he/she will go office late, will not complete his/her on time. That will impact on other employees too.

So for the sake of professional discipline and a proper workflow give it sight on employee’s work ethics.




There are many alternatives approaches you can choose to hire a software developer. We will look upon it all one by one.


1- Traditional way

There was a time when for a software developer role you need a respective degree. But now the technology changes day by with laser light speed developer with current skillset are required.


To learn these skills no college degrees are needed. One can learn these skills from books or join some online courses. So it easy to update your self with the current skillset.


So according to the current trend if you are going to hire a software developer, give priority to experience and knowledge over some professional degrees.


Check Portfolio:-

The best way to check your developer’s experience and skills are the portfolios. As everything is mention in the portfolio you can have knowledge about their past projects.

And the clients for whom they work for. You will get to know what technologies they used in their past projects and experienced them. Or what new skills they are learning and from where.

Then it easy to decide for you. whether the candidate is fulfilling your profile needs or not.


Candidate availability:-

It’s not only developers look for a new opportunity you should also keep an eye on the pool of best developers. Just like if you want to buy any property that is not free now you will keep an on it till then its available for.

Its the same with software developers if you think for some developer that only he/she can get understand your project. And deliver you the best product to keep an eye on him/her. Some developers only available for new recruits when they get a finish from one place.

So if you will track them regularly you get to know when they are going to free and hire them for your project.


2-Hire Remote Developer

Hire a remote developer could be a great option for you. If you are also one of them who can break the geographical boundaries for the sake of technology and skills.

Then this is the best option for you. there are a lot of talented well-skilled developers in the world you just need to shift your eyes from the local talent to the global.

If your project is core technology-based then don’t compromise with the quality of your project just for the ease of hiring local talent. Increase your search area and find the best fit for your project.




Pros of hiring a Remote developer:-

  • You can access to the global talent pool.
  • lots of resources you can get this way.
  • You don’t need to spend on infrastructure and benefit.
  • Costs are potentially lower here.


Cons of hiring a Remote developer:-

  • Chances of data leakage are more.
  • The deadline could be unmet.
  • Time differences could occur.
  • Communication barriers.
  • Unreliability is also an issue here.



So these were some cons and pros of hiring a remote developer. Consider it once if you are going to work with remote developers.


3-Outsourcing companies

If you want to hire a whole new team for your project. And you are not able to get talent what you required. Outsource companies are best for you.

These days finding a skilled software developer is already a difficult task to do upon that you need a whole team to manage that. This could be the most time-consuming part. Outsourcing could help you with this.

You can save your money or not this depends on where you choose to outsource. But definitely, you are going to save a lot of time and resources. That you can use in your project.



Pros of outsourcing:-

  • You can hire multiple developers at once, rather than having to assemble a team one by one.
  • Possible cost saving(depending on the outsourcing location.


Cons of outsourcing:-

  • Loss of managerial control. And insight into the vendor’s process.
  • Low quality of work due to developers working on many projects.
  • Cost overages, as changes to the end product, are priced individually.


These were some pros and cons of outsourcing. Consider them before taking the help of outsourcing companies.


4-Your own development team

There two ways you can make your development team. First the traditional way of hiring second the remote way you can choose likewise.

If you are going to hire local talent for your development team then you have to spend a lot of time and money. Because you needed to hire a few employees then trained them.

Then you need a space where they will work and some other facilities. But if you choose to create a remote development team time and money will-less spend.


Offshoring works Best when:-

  • You need experienced engineers and engineers skilled in rare technologies. But you can’t find them locally.
  • You want to manage the development process directly but not ready to open your own office.
  • You are looking for an open and secure way to grow your development team.


Pros of Offshoring:-

  • Full involvement in the hiring process.
  • Full control over the development process.
  • Full-time developers exclusively working for you.
  • No need to spend on infrastructure.


Cons of offshoring:-

  • Require an inhouse tech specialist, capable of running a development team.
  • Not a good fit for short term projects.
  • 4-6 weeks to build a team.



So these are some methods of hiring software developers. Consider these methods and check which one is the best fit for you.

Categories
Express GraphQL Javascript Node

Nestjs Typeorm Graphql Dataloader tutorial with Typescript


# All source is available here, you can either download 
# or follow the tutorial below to understand 
# each and every component individually  


https://github.com/codersera-repo/typeorm-graphql-nestjs-dataloader-starter-kit 

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.

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.

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”

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
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.

Also Learn TypeORM With NEST JS Basic Tutorial