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: How to Group Functional Components in Reactjs

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: Exploring OneOf in GraphQL Programming

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.

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.

Related Article: AEM GraphQL: A Critical Component in Modern Programming

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.

Additional Resources



- TypeORM Integration with NestJS for GraphQL

- TypeORM Entities and GraphQL

- Creating a Resolver in NestJS for GraphQL

You May Also Like

How to Use Classes in JavaScript

Classes are an essential part of JavaScript programming, allowing you to organize and structure your code more efficiently. In this article, we will … read more

How to Remove Duplicate Values From a JavaScript Array

Removing duplicate values from a JavaScript array can be achieved using different methods. Two popular options are using the Set object and using the… read more

How To Check Checkbox Status In jQuery

jQuery is a powerful JavaScript library that simplifies many common tasks in web development. One such task is checking the status of a checkbox. In … 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 … read more

How to Reload a Page using Javascript

Reloading a web page using Javascript can be achieved through different approaches. One method involves using the location.reload() method, while ano… read more

How to Read an External Local JSON File in Javascript

Reading an external local JSON file in JavaScript can be a process if you follow the right steps. In this article, you will learn how to achieve this… read more

How to Check for String Equality in Javascript

Accurately comparing strings in Javascript is essential for reliable results. This article explores different approaches to check for string equality… read more

How To Loop Through An Array In Javascript

Looping through an array in JavaScript is an essential skill for any developer. In this article, we will explore two common methods for iterating ove… read more

How To Check If A String Contains A Substring In Javascript

Determining if a substring is present in a JavaScript string can be easily accomplished using the includes() or indexOf() methods. This article provi… read more

Sorting Data by Date in GraphQL: A Technical Overview

This article delves into the process of sorting data by date using GraphQL in programming. The article explores how GraphQL works, the purpose of sor… read more