Implementing TypeORM with GraphQL and NestJS

Avatar

By squashlabs, Last Updated: March 20, 2024

Implementing TypeORM with GraphQL and NestJS

TypeORM Decorators

TypeORM is an Object Relational Mapping (ORM) library that simplifies database interactions in Node.js applications. It provides decorators to define and configure entities, columns, relationships, and more. These decorators help TypeORM map your JavaScript/TypeScript objects to database tables and columns.

Let’s start by looking at some commonly used TypeORM decorators:

1. @Entity: The @Entity decorator is used to mark a class as an entity. An entity represents a database table and its properties represent table columns. Here’s an example:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

In the above example, the User class is marked as an entity using the @Entity decorator. The id, name, and email properties are marked as columns using the @Column decorator.

2. @PrimaryGeneratedColumn: The @PrimaryGeneratedColumn decorator is used to mark a property as the primary key of an entity. It also specifies that the value of the column will be automatically generated. Here’s an example:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

In the above example, the id property is marked as the primary key using the @PrimaryGeneratedColumn decorator.

3. @Column: The @Column decorator is used to mark a property as a column in the entity’s table. It allows you to specify various options such as column type, length, default value, and more. Here’s an example:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ unique: true })
  email: string;

  @Column({ default: false })
  isAdmin: boolean;
}

In the above example, the name and email properties are marked as columns using the @Column decorator. The email column is marked as unique, and the isAdmin column has a default value of false.

These are just a few examples of the decorators provided by TypeORM. You can explore more decorators such as @ManyToOne, @OneToMany, @ManyToMany, and more to define relationships between entities.

Related Article: nvm (Node Version Manager): Install Guide & Cheat Sheet

TypeORM Resolvers

TypeORM Resolvers are responsible for handling GraphQL queries and mutations for your entities. They define the operations that can be performed on your entities and interact with the TypeORM entities and repositories to retrieve and manipulate data.

To create a resolver for an entity, you typically follow these steps:

1. Import necessary dependencies:

import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

2. Use the @Resolver decorator to mark the class as a resolver:

@Resolver()
export class UserResolver {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}
}

3. Define GraphQL queries and mutations using decorators such as @Query and @Mutation:

@Resolver()
export class UserResolver {
  // ...

  @Query(() => [User])
  async users(): Promise<User[]> {
    return this.userRepository.find();
  }

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

  // ...
}

In the above example, the users query returns an array of User entities by calling the find method on the userRepository. The createUser mutation creates a new User entity by calling the create and save methods on the userRepository.

You can also include arguments in your queries and mutations using the @Args decorator. The arguments are automatically injected into the resolver function.

TypeORM Entities

In TypeORM, entities represent database tables and their properties represent table columns. They are used to define the structure and behavior of the data stored in the database.

To create an entity in TypeORM, you typically follow these steps:

1. Create a new TypeScript class and mark it as an entity using the @Entity decorator:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}

In the above example, the User class is marked as an entity using the @Entity decorator. The id, name, and email properties are marked as columns using the @Column decorator.

2. Optionally, you can define relationships between entities using decorators such as @ManyToOne, @OneToMany, @ManyToMany, and more. Here’s an example of a Post entity with a many-to-one relationship with the User entity:

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  content: string;

  @ManyToOne(() => User, user => user.posts)
  author: User;
}

In the above example, the Post entity has a many-to-one relationship with the User entity. The author property is marked with the @ManyToOne decorator, which specifies the target entity (User) and the inverse side property (posts) in the User entity.

3. Repeat the above steps for other entities in your application.

Entities are the backbone of your TypeORM application. They define the structure of your database and provide a way to interact with the data stored in it.

TypeORM Mutations

In TypeORM with NestJS and GraphQL, mutations are used to modify data in the database. Mutations typically correspond to create, update, and delete operations.

To implement mutations in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Create a new resolver class and import necessary dependencies:

import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

2. Use the @Resolver decorator to mark the class as a resolver:

@Resolver()
export class UserResolver {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  // Mutations go here
}

3. Define mutation methods using the @Mutation decorator:

@Resolver()
export class UserResolver {
  // ...

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

  @Mutation(() => User)
  async updateUser(@Args('id') id: number, @Args('name') name: string, @Args('email') email: string): Promise<User> {
    const user = await this.userRepository.findOne(id);
    if (!user) {
      throw new Error('User not found');
    }
    user.name = name;
    user.email = email;
    return this.userRepository.save(user);
  }

  @Mutation(() => Boolean)
  async deleteUser(@Args('id') id: number): Promise<boolean> {
    const user = await this.userRepository.findOne(id);
    if (!user) {
      throw new Error('User not found');
    }
    await this.userRepository.remove(user);
    return true;
  }

  // ...
}

In the above example, the createUser mutation creates a new User entity by calling the create and save methods on the userRepository. The updateUser mutation updates an existing User entity by finding it using the findOne method and modifying its properties. The deleteUser mutation deletes a user by finding it using the findOne method and removing it using the remove method.

You can define mutations with any number of arguments and return types. The @Args decorator is used to specify the input arguments of the mutation. The return type of the mutation is specified using the @Mutation decorator.

Related Article: How to Use the forEach Loop with JavaScript

TypeORM Subscriptions

TypeORM with NestJS and GraphQL supports real-time data updates using subscriptions. Subscriptions allow clients to subscribe to specific events or data changes and receive updates in real-time. This is useful for implementing features such as real-time notifications or chat applications.

To implement subscriptions in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Install the necessary dependencies:

npm install graphql-subscriptions apollo-server-express subscriptions-transport-ws

2. Create a new resolver class and import necessary dependencies:

import { Resolver, Subscription, Inject } from '@nestjs/graphql';
import { PubSubEngine } from 'graphql-subscriptions';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

3. Use the @Resolver decorator to mark the class as a resolver:

@Resolver()
export class UserResolver {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @Inject('PUB_SUB')
    private pubSub: PubSubEngine,
  ) {}

  // Subscriptions go here
}

4. Define subscription methods using the @Subscription decorator:

@Resolver()
export class UserResolver {
  // ...

  @Subscription(() => User, {
    filter: (payload, variables) => {
      // Implement filtering logic here
      return payload.userId === variables.userId;
    },
  })
  userCreated() {
    return this.pubSub.asyncIterator('userCreated');
  }

  // ...
}

In the above example, the userCreated subscription returns a user whenever a new user is created. The pubSub.asyncIterator method is used to create a new subscription channel named 'userCreated'. The filter function is used to filter the subscriptions based on payload and variables.

5. Publish events to trigger subscriptions:

import { PubSub } from 'graphql-subscriptions';

// ...

@Resolver()
export class UserResolver {
  constructor(
    // ...
    @Inject('PUB_SUB')
    private pubSub: PubSubEngine,
  ) {}

  @Mutation(() => User)
  async createUser(@Args('name') name: string, @Args('email') email: string): Promise<User> {
    const user = this.userRepository.create({ name, email });
    const savedUser = await this.userRepository.save(user);
    await this.pubSub.publish('userCreated', { userId: savedUser.id });
    return savedUser;
  }

  // ...
}

In the above example, the createUser mutation publishes a 'userCreated' event after saving the user. This triggers the userCreated subscription and sends the new user to the subscribed clients.

Subscriptions allow you to implement real-time features in your TypeORM with NestJS and GraphQL application. You can create subscriptions for various events and data changes and use the pubSub instance to publish events and trigger subscriptions.

TypeORM Directives

TypeORM with NestJS and GraphQL allows you to use directives to customize the behavior of your GraphQL schema and resolvers. Directives can be used to modify the execution of queries, mutations, and subscriptions based on certain conditions or requirements.

To use directives in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Install the necessary dependencies:

npm install type-graphql graphql-tools graphql

2. Create a new decorator to represent a directive:

import { SchemaDirectiveVisitor } from 'graphql-tools';
import { defaultFieldResolver, GraphQLField } from 'graphql';

export class UpperCaseDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field: GraphQLField<any, any>) {
    const { resolve = defaultFieldResolver } = field;

    field.resolve = async function (...args) {
      const result = await resolve.apply(this, args);

      if (typeof result === 'string') {
        return result.toUpperCase();
      }

      return result;
    };
  }
}

In the above example, a custom directive named @upper is defined. This directive converts the value of a field to uppercase. It uses the SchemaDirectiveVisitor class from graphql-tools to visit and modify the field definition.

3. Apply the directive to a field in your entity:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { Field, ID, ObjectType } from 'type-graphql';
import { UpperCaseDirective } from './uppercase.directive';

@ObjectType()
@Entity()
export class User {
  @Field(() => ID)
  @PrimaryGeneratedColumn()
  id: number;

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

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

  @Field()
  @Column()
  @Directive('@upper')
  city: string;
}

In the above example, the city field is marked with the @upper directive. This will cause the field resolver to apply the uppercase conversion defined in the UpperCaseDirective.

4. Create a new entity resolver and import necessary dependencies:

import { Resolver, Query } from 'type-graphql';
import { getRepository } from 'typeorm';
import { User } from './user.entity';

@Resolver(() => User)
export class UserResolver {
  private userRepository = getRepository(User);

  @Query(() => [User])
  users(): Promise<User[]> {
    return this.userRepository.find();
  }
}

5. Register the directive and resolver in your application:

import { ApolloServer } from 'apollo-server-express';
import { buildSchema } from 'type-graphql';
import { UpperCaseDirective } from './uppercase.directive';
import { UserResolver } from './user.resolver';

async function bootstrap() {
  const schema = await buildSchema({
    resolvers: [UserResolver],
    schemaDirectives: {
      upper: UpperCaseDirective,
    },
  });

  const server = new ApolloServer({ schema });

  // ...
}

In the above example, the UpperCaseDirective is registered as a schema directive using the schemaDirectives option in the buildSchema function.

Directives provide a way to customize the behavior of your GraphQL schema and resolvers in TypeORM with NestJS and GraphQL. You can create custom directives to modify the execution of fields, arguments, or entire operations based on specific conditions or requirements.

TypeORM Interfaces

In TypeORM with NestJS and GraphQL, interfaces allow you to define common fields and functionality that can be shared across multiple entities. Interfaces provide a way to enforce a contract and ensure that entities implementing the interface have the required properties and methods.

To define and use interfaces in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Create a new TypeScript interface:

import { Field, ID, InterfaceType } from 'type-graphql';

@InterfaceType()
export abstract class BaseEntity {
  @Field(() => ID)
  id: number;
}

In the above example, the BaseEntity interface is defined using the @InterfaceType decorator from type-graphql. The interface has a single field id of type ID.

2. Implement the interface in your entity:

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { Field, ObjectType } from 'type-graphql';
import { BaseEntity } from './base.entity';

@ObjectType({ implements: BaseEntity })
@Entity()
export class User implements BaseEntity {
  @Field()
  @PrimaryGeneratedColumn()
  id: number;

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

  @Field()
  @Column()
  email: string;
}

In the above example, the User entity implements the BaseEntity interface using the implements option in the @ObjectType decorator. The id property is defined in the User entity to satisfy the requirements of the BaseEntity interface.

3. Use the interface in your resolvers:

import { Resolver, Query } from 'type-graphql';
import { getRepository } from 'typeorm';
import { BaseEntity } from './base.entity';

@Resolver(() => BaseEntity)
export class BaseEntityResolver {
  private baseEntityRepository = getRepository(BaseEntity);

  @Query(() => [BaseEntity])
  baseEntities(): Promise<BaseEntity[]> {
    return this.baseEntityRepository.find();
  }
}

In the above example, the BaseEntity interface is used as the return type in the resolver for the baseEntities query.

Interfaces allow you to define common fields and functionality that can be shared across multiple entities in TypeORM with NestJS and GraphQL. They provide a way to enforce a contract and ensure that entities implementing the interface have the required properties and methods.

Related Article: How to Use Javascript Substring, Splice, and Slice

Handling Context in TypeORM

In TypeORM with NestJS and GraphQL, the context is an object that is shared across all resolvers within a single GraphQL operation. It allows you to pass data or services that are needed by multiple resolvers, such as authentication information, database connections, or other dependencies.

To handle the context in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Define the context type:

import { Request } from 'express';

export interface MyContext {
  req: Request;
  userId: number;
}

In the above example, the MyContext interface is defined with a req property of type Request from the express library, and a userId property of type number.

2. Create a GraphQL context provider:

import { Injectable, ExecutionContext } from '@nestjs/common';
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';
import { MyContext } from './my-context.interface';

@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext): any {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext<MyContext>().req;
  }
}

In the above example, the MyAuthGuard class extends the AuthGuard class from @nestjs/passport and overrides the getRequest method to extract the Request object from the GraphQL context.

3. Use the context provider in your resolvers:

import { Resolver, Query, Context } from '@nestjs/graphql';
import { MyContext } from './my-context.interface';

@Resolver()
export class UserResolver {
  @Query(() => User)
  me(@Context() ctx: MyContext): Promise<User> {
    const userId = ctx.userId;
    // Query user data based on the userId
  }
}

In the above example, the me query resolver uses the @Context decorator to inject the MyContext object into the resolver function. The userId property can then be accessed from the context to perform user-specific operations.

The context is a useful mechanism in TypeORM with NestJS and GraphQL that allows you to share data or services across multiple resolvers within a single GraphQL operation. It enables you to pass information or dependencies that are needed by multiple resolvers in an efficient and structured way.

TypeORM Pagination

Pagination is a common requirement in web applications to display large sets of data in smaller, more manageable chunks. TypeORM with NestJS and GraphQL provides built-in support for pagination using the PaginationType from typeorm-pagination.

To implement pagination in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Install the necessary dependencies:

npm install typeorm-pagination

2. Create a new resolver class and import necessary dependencies:

import { Resolver, Query, Args } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { PaginationArgs } from './pagination.args';
import { PaginationType } from 'typeorm-pagination';

3. Use the @Resolver decorator to mark the class as a resolver:

@Resolver()
export class UserResolver {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  // Queries go here
}

4. Define a query method with pagination arguments:

@Resolver()
export class UserResolver {
  // ...

  @Query(() => PaginationType(User))
  async users(@Args() args: PaginationArgs): Promise<PaginationType<User>> {
    return this.userRepository.paginate(args);
  }

  // ...
}

In the above example, the users query accepts pagination arguments using the @Args decorator and returns a PaginationType<User>. The userRepository.paginate method is used to retrieve paginated results based on the provided arguments.

5. Create a new arguments class for pagination:

import { ArgsType, Field, Int } from '@nestjs/graphql';

@ArgsType()
export class PaginationArgs {
  @Field(() => Int, { defaultValue: 1 })
  page: number;

  @Field(() => Int, { defaultValue: 10 })
  limit: number;
}

In the above example, the PaginationArgs class is created using the @ArgsType decorator. It defines two fields: page and limit. The defaultValue option is used to provide default values for these fields.

You can now use the users query with pagination arguments to retrieve paginated results from the userRepository.

Pagination is a useful feature in TypeORM with NestJS and GraphQL to efficiently retrieve and display large sets of data. It allows you to control the number of results per page and the current page, making it easier to navigate and manage large datasets.

TypeORM Batching

Batching is a technique used to optimize database queries by combining multiple individual queries into a single batch query. TypeORM with NestJS and GraphQL provides support for batching using the dataloader library.

To implement batching in TypeORM with NestJS and GraphQL, you can follow these steps:

1. Install the necessary dependencies:

npm install dataloader

2. Create a new resolver class and import necessary dependencies:

import { Resolver, Query, Args } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { Loader } from 'nestjs-dataloader';

3. Use the @Resolver decorator to mark the class as a resolver:

@Resolver()
export class UserResolver {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @Loader('UserLoader')
    private userLoader: DataLoader<number, User>,
  ) {}

  // Queries go here
}

4. Define a query method to load users:

@Resolver()
export class UserResolver {
  // ...

  @Query(() => User)
  async user(@Args('id') id: number): Promise<User> {
    return this.userLoader.load(id);
  }

  // ...
}

In the above example, the user query method uses the userLoader to load a user by calling the load method with the user’s id. The userLoader is injected using the @Loader decorator and is configured with the key 'UserLoader'.

5. Create a dataloader function to batch load users:

import DataLoader from 'dataloader';
import { User } from './user.entity';

export const createUserLoader = (userRepository: Repository<User>): DataLoader<number, User> =>
  new DataLoader<number, User>(async (ids: number[]) => {
    const users = await userRepository.findByIds(ids);
    const userMap: Record<number, User> = {};
    users.forEach((user) => {
      userMap[user.id] = user;
    });
    return ids.map((id) => userMap[id]);
  });

In the above example, the createUserLoader function creates a new DataLoader instance and provides a batch loading function. The batch loading function retrieves users from the userRepository using the findByIds method and returns them in the same order as the provided ids.

You can now use the user query method to load users, and the dataloader will automatically batch and cache the database queries, resulting in improved performance.

Related Article: JavaScript Arrays: Learn Array Slice, Array Reduce, and String to Array Conversion

Additional Resources

TypeORM Integration with NestJS for GraphQL
TypeORM Entities and GraphQL
Creating a Resolver in NestJS for GraphQL

You May Also Like

JavaScript HashMap: A Complete Guide

This guide provides an essential understanding of implementing and utilizing HashMaps in JavaScript projects. This comprehensive guide covers everything from creating... read more

Conditional Flow in JavaScript: Understand the ‘if else’ and ‘else if’ Syntax and More

Conditional Flow in JavaScript: Understand the 'if else' and 'else if' Syntax and More Gain clarity on logical conditions and enhance your JavaScript development by... read more

JavaScript Arrow Functions Explained (with examples)

JavaScript arrow functions are a powerful feature that allows you to write concise and elegant code. In this article, you will learn the basics of arrow functions and... read more

JavaScript Modules & How to Reuse Code in JavaScript

JavaScript modules are a powerful tool for organizing and reusing code in your JavaScript projects. In this article, we will explore various aspects of JavaScript... read more

High Performance JavaScript with Generators and Iterators

JavaScript generators and iterators are powerful tools that can greatly enhance the performance of your code. In this article, you will learn how to use generators and... read more

JavaScript Objects & Managing Complex Data Structures

JavaScript objects are a fundamental part of the language and understanding how to use them is essential for any JavaScript developer. This article provides a clear... read more