Categoriesgraphqlnode orm libraries

Typeorm with GraphQL ( type-graphql) and express

This tutorial will help you to understand the integration of typeorm with graphql(type-graphql)

This article will guide you on how you can integrate typeorm with type-graphql. After following this article you don’t need to worry about the integration and standards. I will teach you according to the standards that you can implement in your professional project. 

This article is mostly focused on the integration of typeorm and type-graphql and it will touch typeorm and type-graphql generally instead of going into the detail. So if you want to learn in detail you can check my typeorm and type-graphql tutorial

Typeorm Tutorial in detail with code examples

Type-graphql tutorial in detail with code examples

Final Code is attached at the end of the article but you can download the basic code setup with all of the necessary dependencies.

GitHub – mahmedyoutube/typeorm-with-typegraphql at basic-project-setup

TypeOrm

It is a TypeScript ORM library that helps to link your TypeScript application with a relational database. In this topic, we will cover the following content

  1. Entity creation with relations
  2. Database connection

Entity Creation with relations

We are going to create 4 entities User, Cart, Product, and Profile. First I will explain to you the basic terms 

  1. @Entity() used to create a new table in a database
  2. @Column() used to create a new column in a table
  3. @PrimaryGeneratedColumn() is used to add a primary column in a table
  4. @CreateDateColumn() to help you to create the createdAt column in the table which will help you to track when the record was created.
  5. @UpdateDateColumn() will update the specific column (updatedAt) every time when you update the record
  6. @OneToOne(() => Profile, (profile) => profile.user) it is used to set a one-to-one relation with a profile. The arrow function is passed to set the relation with another entity. and the second param is used to set the bi-direction relation with another entity column. If you don’t pass the second parameter then it means you are setting the uni-direction relation
  7. @OneToMany(() => Product, (product) => product.user) it’s mean you current class can belong to multiple records of the product but multiple products belong to a single class
  8. @ManyToOne(() => User, (user) => user.products) the same explanation as the above point. The current class has multiple records but belongs to a single User 
  9. @JoinColumn to set custom reference columnName. And it is an optional decorator
  10. @ManyToMany(() => B) current class A has multiple instances of B and the B can belong to multiple instances of the class A
  11. @JoinTable() necessary for many-to-many relationship to join the table

Code Examples

User Entity code

import {
  Column,
  CreateDateColumn,
  Entity,
  OneToMany,
  OneToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
  BaseEntity
} from "typeorm";
import { Profile } from "./Profile";
import { Product } from "./Product";


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

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  email: string;

  @Column()
  password: string;

  @Column()
  phoneNumber?: string;

  @OneToOne(() => Profile, (profile) => profile.user)
  profile: Profile;

  @OneToMany(() => Product, (product) => product.user)
  products: Product[];
}

Profile Entity Code

import {
  Column,
  CreateDateColumn,
  Entity,
  OneToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { User } from "./User";

import { BaseEntity } from "typeorm";


@Entity()
export class Profile extends BaseEntity{
  @PrimaryGeneratedColumn()
  id: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column()
  profilePic: string;

  @Column()
  aboutMe: string;

  @OneToOne(() => User, (user) => user.profile)
  user: User;
}

Product Entity Code

import {
  Column,
  CreateDateColumn,
  Entity,
  JoinColumn,
  ManyToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { User } from "./User";
import { BaseEntity } from "typeorm";

@Entity()
export class Product extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column()
  productPic: string;

  @Column()
  productDescription: string;

  @Column({ type: "numeric" })
  price: number;

  @ManyToOne(() => User, (user) => user.products)
  @JoinColumn({ name: "user_id" })
  user: User;
}

Cart Entity Code

import { JoinTable, ManyToMany } from "typeorm";
import { Product } from "./Product";

import { BaseEntity } from "typeorm";

@Entity()
export class Cart extends BaseEntity{

  @PrimaryGeneratedColumn()
  id: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
  
  @ManyToMany(() => Product)
  @JoinTable()
  product: Product[];
}

Database Connection in type-orm

this._appDataSource = new DataSource({
      type: config.dbType,
      host: config.dbHost!,
      port: config.dbPort,
      username: config.dbUsername,
      password: config.dbPassword,
      database: config.dbName,
      entities: this.entities, // here you can pass all of your entities like [User, Product]
      synchronize: true, // makes the entity sync with the database every time you run your application
    });

try {
      await this._appDataSource.initialize();
      console.log("Data Source has been initialized!");
    } catch (err) {
      console.error("Error during Data Source initialization", err);
    }

Type-graphql

It is used to build graphql API’s with node and typeScript. It helps to define schema directly from our typescript code.

Before Creating schemas, I am sharing a few terminologies concepts with you so you can’t confuse by reading the code

  1. @ObjectType() creates a new Schema in graphql
  2. @Field() creates a new field in a schema and if you want to make the field optional then you can pass { nullable: false } in Field function. 
  3. @Field((type) => [Product], { nullable: true }) It means you’re current field in a particular schema either returns an array of Product or null. 
  4. @Field((type) => Product, { nullable: true }) It means you’re current field in a particular schema either returns a Product or null.

Now we will add type-graphql decorators in typeorm class also so we can build a graphql schema. 

Entity schemas in type-graphql

You only need to add @Field() in an entity class below the @column() decorators and @objectType() below the @Entity() decorators and the rest of the things will be handled by type-graphql. 

User Schema 

import {
  Column,
  CreateDateColumn,
  Entity,
  JoinColumn,
  OneToMany,
  OneToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { Profile } from "./Profile";
import { Product } from "./Product";
import { Field, ObjectType } from "type-graphql";
import { BaseEntity } from "typeorm";
@Entity()
@ObjectType()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field()
  id: number;

  @CreateDateColumn()
  @Field()
  createdAt: Date;

  @UpdateDateColumn()
  @Field()
  updatedAt: Date;

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

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

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

  @Column()
  password: string;

  @Column({ nullable: true })
  @Field({ nullable: true })
  phoneNumber?: string;

  @Field({ nullable: true }) // it's not a column
  token?: string;

  @OneToOne(() => Profile, (profile) => profile.user)
  @JoinColumn()
  @Field(() => Profile)
  profile: Profile;

  @OneToMany(() => Product, (product) => product.user)
  @Field(() => [Product], { nullable: true })
  products: Product[];
}

Product Schema

import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  JoinColumn,
  JoinTable,
  ManyToMany,
  ManyToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { User } from "./User";
import { Field, ObjectType } from "type-graphql";
import { Cart } from "./Cart";

@Entity()
@ObjectType()
export class Product extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field()
  id: number;

  @CreateDateColumn()
  @Field()
  createdAt: Date;

  @UpdateDateColumn()
  @Field()
  updatedAt: Date;

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

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

  @Column({ type: "numeric" })
  @Field()
  price: number;

  @ManyToOne(() => User, (user) => user.products)
  @JoinColumn() //   if user want to change the name of the column then pass {name: "user_id"} in JoinColumn function
  @Field(() => User)
  user: User;

  @Field(() => [Cart], { nullable: true })
  cart: Cart[];
}

Profile Schema

import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  OneToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { User } from "./User";
import { Field, ObjectType } from "type-graphql";

@Entity()
@ObjectType()
export class Profile extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field()
  id: number;

  @CreateDateColumn()
  @Field()
  createdAt: Date;

  @UpdateDateColumn()
  @Field()
  updatedAt: Date;

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

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

  @OneToOne(() => User, (user) => user.profile)
  @Field(() => User)
  user: User;
}

Cart Schema

import {
  BaseEntity,
  CreateDateColumn,
  Entity,
  JoinTable,
  ManyToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
import { Product } from "./Product";
import { Field, ObjectType } from "type-graphql";

@Entity()
@ObjectType()
export class Cart extends BaseEntity {
  @PrimaryGeneratedColumn()
  @Field()
  id: number;

  @CreateDateColumn()
  @Field()
  createdAt: Date;

  @UpdateDateColumn()
  @Field()
  updatedAt: Date;

  @ManyToMany(() => Product)
  @JoinTable()
  @Field((type) => [Product])
  products: Product[];
}

Schema resolver in type-graphql

Just like schema, you also need to understand some basic concepts related to the resolver

  1. @Query(() => ReturnSchema) used to create a query resolver and the arrow function defines what will be returned. It also accepts second args which means you can pass optional params also like {nullable:true} which means your query can return a null value. 
  2. @Mutation(() => User) used to create a mutation resolver and the arrow function defines what will be returned. 
  3. @Arg(“argname”)it is used to pass a param in a function
  4. @FieldResolver(() => ReturnSchema) used to create a resolver on a field level and the arrow function defines what will be returned. It also accepts second args which means you can pass optional params also like {nullable:true} which means your query can return a null value.
  5. @Root() is used with fieldResolver to fetch the parent value from the parent resolver ( query or mutation )
  6. @ArgsType() If you want to pass multiple args in a function you can build a separate argsType class and then pass it to the function. It will help you a lot in architecture and extending the application

If you are using the typeorm, you don’t need to specify the field resolver instead of it you can fetch all the related records in typeorm easily and rest of the thing will be handled by typegraphql easily

Example

 @Query(() => [Product], { nullable: true })
  async products(): Promise<Product[] | null> {
    return await Product.find({
      relations: {
        user: { profile: { user: true }, products: true, }, // it will fetch user record and profile record must be present in user record, same for profile
        cart: true, // you are only fetching the cart record and no inner record will be present in cart like cart { products {}} 
      },
    });
  }

User Resolver

import {
  Arg,
  Args,
  FieldResolver,
  Mutation,
  Query,
  Resolver,
  Root,
} from "type-graphql";

import { CreateNewUserArgs } from "./types";
import { Product } from "../../schema/Product";
import { User } from "../../schema/User";
import { encodePassword, generateToken } from "../../../utils/helpers";
import { Profile } from "../../schema/Profile";

@Resolver(() => User)
export class UserResolver {
  @Query(() => [User], { nullable: true })
  async users(): Promise<User[] | null> {
    const allUsers = await User.find({
      relations: { products: true, profile: true },
    });
    return allUsers;
  }

  @Mutation(() => User)
  async createUser(
    @Args()
    { firstName, lastName, email, password, phoneNumber }: CreateNewUserArgs
  ) {
    // create profile record with user with default value, so later user can update his record
    const profile = Profile.create({
      profilePic: "..", // default value
      aboutMe: "..", // default value
    });

    const user = User.create({
      firstName,
      lastName,
      email,
      password: await encodePassword(password),
      phoneNumber,
    });

    await user.save();

    await profile.save();

    // update user record again with profile record
    user.profile = profile;
    await user.save();

    const token = await generateToken({ email: user.email, id: user.id });

    return { ...user, token };
  }
}

User resolver ArgsType

import { ArgsType, Field } from "type-graphql";

@ArgsType()
export class CreateNewUserArgs {
  @Field({ nullable: false })
  firstName: string;
  @Field({ nullable: false })
  lastName: string;

  @Field((type) => String, { nullable: false })
  email: string;

  @Field({ nullable: false })
  password: string;

  @Field({ nullable: true })
  phoneNumber?: string;
}

Profile Resolver

import { Authorized, Ctx, Query, Resolver } from "type-graphql";

import { User } from "../../schema/User";
import { Profile } from "../../schema/Profile";
import { IGraphQLCotext } from "../../../global/types";

@Resolver(() => Profile)
export class ProfileResolver {
  @Authorized()
  @Query(() => Profile, { nullable: true })
  async profile(
    @Ctx() { currentUser }: IGraphQLCotext
  ): Promise<Profile | null> {
    const user = await User.findOne({
      where: { id: currentUser!.id },
      relations: { profile: true },
    });

    return user!.profile;
  }
}

Product Resolver with ArgsTypeClass

import {
  Args,
  Authorized,
  Ctx,
  Mutation,
  Query,
  Resolver,
} from "type-graphql";
import { CreateNewProductArgs } from "./types";
import { Product } from "../../schema/Product";
import { User } from "../../schema/User";
import { IGraphQLCotext } from "../../../global/types";

@Resolver(() => User)
export class ProductResolver {
  @Query(() => [Product], { nullable: true })
  async products(): Promise<Product[] | null> {
    return await Product.find({
      relations: {
        user: { profile: { user: true }, products: true, }, // it will fetch user record and profile record must be present in user record, same for profile
        cart: true, // you are only fetching the cart record and no inner record will be present in cart like cart { products {}} 
      },
    });
  }

  @Authorized()
  @Mutation(() => Product, { nullable: true })
  async createProduct(
    @Args()
    { pic, description, price }: CreateNewProductArgs,
    @Ctx() { currentUser }: IGraphQLCotext
  ) {
    const user = await User.findOneBy({ id: currentUser!.id });
    const product = Product.create({ pic, description, price, user: user! });
    await product.save();
    return product;
  }
}
import { ArgsType, Field, Int } from "type-graphql";

@ArgsType()
export class CreateNewProductArgs {
  @Field()
  pic: string;
  @Field()
  description: string;
  @Field()
  price: number;
}

Cart Resolver

import {
  Arg,
  Args,
  Authorized,
  Ctx,
  FieldResolver,
  Mutation,
  Query,
  Resolver,
  Root,
} from "type-graphql";
import { Product } from "../../schema/Product";
import { User } from "../../schema/User";
import { IGraphQLCotext } from "../../../global/types";
import { Cart } from "../../schema/Cart";

@Resolver(() => User)
export class CartResolver {
  @Query(() => [Cart], { nullable: true })
  async carts(): Promise<Cart[] | null> {
    return await Cart.find({
      relations: {
        products: {
          user: {
            profile: true,
          },
        },
      },
    });
  }

  @Authorized()
  @Mutation(() => Cart, { nullable: true })
  async addProductToCart(
    @Arg("productId")
    productId: number
  ) {
    const product = await Product.findOneBy({ id: productId });

    const cart = Cart.create({ products: [product!] });
    await cart.save();

    return cart;
  }
}

Complete Source code

Github link to download the source code of typeorm integration with type-graphql

Conclusion

In short, in this article, we learn about basic typeorm and type-graphql. It also explains the integration of typeorm with graphql(type-graphql) with code examples. If you still have a question you can reach out to me on LinkedIn. I will try my best to answer your question

Thank you for reading my article.

M Ahmed Mushtaq

Categoriesgraphqlnode orm libraries

Type-graphql tutorial with code examples

This tutorial is specially designed for those people who try to learn new concepts with the help of programming without going too much into the theory. 

It is used to build graphql API’s with node and typeScript. It helps to define schema directly from our typescript code. 

In this tutorial, we will cover the following topics

  1. Type-graphql setup 
  2. Detail schema and resolver overview
  3. Relations in graphql

General steps to set up the type-graphql 

  1. Create Express server
  2. Create a Resolver with schema
  3. Build Schema by using buildSchema function from type-graphql
  4. Create an apollo server by passing schema in the apollo server
  5. Start the apollo server before the express server

Step 1

Final Code is attached at the end of the article but you can download the basic code setup with all of the necessary dependencies.

https://github.com/mahmedyoutube/typeorm-with-typegraphql/tree/basic-project-setup

Step 2

Now we are creating the user schema and resolver. Creating a schema in type-graphql is very easy just like type-orm

  1. @ObjectType() creates a new Schema in graphql
  2. @Field() creates a new field in a schema and if you want the field optional then you can pass { nullable: false } in Field function

Don’t worry if you find difficulty to grasp this concept, I will explain it again in next section. 

Simple type-graphql schema code example

import { Field, ObjectType } from "type-graphql";

@ObjectType()
export class UserSchema {
  @Field()
  id: number;

  @Field()
  createdAt: Date;

  @Field()
  updatedAt: Date;

  @Field()
  firstName: string;

  @Field()
  lastName: string;

  @Field()
  email: string;

  @Field()
  password: string;

  @Field()
  phoneNumber?: string;
}

Simple type-graphql resolver code example

  1. @Query() is used to create a Query resolver. Its first param defines what will be the return type. In the second param, you can pass many optional params but by passing {nullable:true} means you are expecting a null value from the query function
import { Query, Resolver } from "type-graphql";
import { UserSchema  as User} from "../../schema/UserSchema";

@Resolver(() => User)
export class UserResolver {
  @Query(() => [User], { nullable: true })
  async users(): Promise<User[] | null> {
    return [];
  }
}

Step 3

Now we will buildSchema by using buildSchema type-graphql function.

await buildSchema({
resolvers: [UserResolver],
validate: { forbidUnknownValues: false },
nullableByDefault: false, // by default all fields are not optional
});

Step 4

Basic code to set up the apollo server. First, build the schema and then pass it to graphql.

const schema = await buildSchema({
      resolvers: [UserResolver],
      validate: { forbidUnknownValues: false },
      nullableByDefault: false, // by default all fields are not optional
    });

    const apolloServer = new ApolloServer({
      schema,
      context: ({ req }) => {
        return {
          req,
        };
      },
    });
    await apolloServer.start();
    apolloServer.applyMiddleware({ app });

Complete Code Example with schema and apollo server. This example is using the class-based approach. You can convert it into a functional-based approach

import { NonEmptyArray, buildSchema } from "type-graphql";
import { GraphQLSchema } from "graphql";
import { UserResolver } from "./resolvers/UserResolver";
import { ApolloServer } from "apollo-server-express";
import { Express } from "express";

class GraphQl {
  private _schema: GraphQLSchema;

  private resolvers(): NonEmptyArray<Function> | NonEmptyArray<string> {
    return [UserResolver];
  }

  async buildGraphQLSchema() {
    this._schema = await buildSchema({
      resolvers: this.resolvers(),
      validate: { forbidUnknownValues: false },
      nullableByDefault: false, // by default all fields are not optional
    });
  }

  async startApolloServer(app: Express) {
    await this.buildGraphQLSchema();
    const apolloServer = new ApolloServer({
      schema: this.schema,
      context: ({ req }) => {
        return {
          req,
        };
      },
    });
    await apolloServer.start();
    apolloServer.applyMiddleware({ app });
  }

  get schema() {
    return this._schema!;
  }
}

const graphQl = new GraphQl();

export default graphQl;

Step 5

Main Server file 

import express from "express";
import { db } from "./database/db";
import graphQl from "./graphql";

const app = express();

db.connect();
app.use(express.json());

graphQl.startApolloServer(app);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(
    `server is listening on the port ${PORT} graphql: http://localhost:${PORT}/graphql`
  );
});

Complete Code to setup type-graphql in express

GitHub — mahmedyoutube/typeorm-with-typegraphql at type-graphql-project-setup

Detailed overview of the schema and resolvers in graphql

Schema

It describes the shape of your available data.

How you can define a schema in type-graphql?

You can follow the following steps to define the schema in type-graphql

  1. Create a Class
  2. Add @ObjectType() decorator at the top of the class
  3. Add fields in a class with @Field() decorator

Example

import { Field, ObjectType } from "type-graphql";

@ObjectType()
export class UserSchema {
  @Field()
  id: number;

  @Field()
  createdAt: Date;

  @Field()
  updatedAt: Date;

  @Field()
  firstName: string;

  @Field()
  lastName: string;

  @Field()
  email: string;

  @Field()
  password: string;

  @Field()
  phoneNumber?: string;
}

Resolvers

It is a function that resolves a value for a type or field within a schema

How you can define a resolver in type-graphql?

You can follow the following steps to define the schema in type-graphql

  1. Create a Class
  2. Add @Resolver(() => YourSchemaClassName) decorator at the top of the class
  3. To build a query resolver, add@Query(() => YourSchemaClassName) decorator at the top of the function.
  4. Same way for mutation just like query resolver. But in mutation, you need to understand some other concepts also like adding @Arg() in param so type-graphql can know that you are accepting the particular argument in your mutation function

Examples

  1. Basic Query resolver example
import { Query, Resolver } from "type-graphql";
import { UserSchema  as User} from "../../schema/UserSchema";

@Resolver(() => User)
export class UserResolver {

//[User] mean return result will consist of array of users, or you can imagine like typescript user[]

@Query(() => [User], { nullable: true }) // nullable mean your query can return null 

async users(): Promise<User[] | null> {
    return [];
  }
}

2. Basic Mutation example

import { Arg, Mutation, Query, Resolver } from "type-graphql";
import { UserSchema as User } from "../../schema/UserSchema";

@Resolver(() => User)
export class UserResolver {
  
  @Mutation(() => User)
  async createUser(@Arg("firstName") firstName: string) {
    console.log("firstName ", firstName);
  }
}

If you want to pass multiple arguments in mutation function, then it is preferable to create a new ArgsType class

example

ArgsType class example

import { ArgsType, Field } from "type-graphql";

@ArgsType()
export class CreateNewUserArgs{
  @Field({ nullable: false })
  firstName: string;

  @Field({ nullable: false })
  lastName: string;

  @Field((type) => String, { nullable: false })
  email: string;

  @Field({ nullable: false })
  password: string;

  @Field({ nullable: true })
  phoneNumber?: string;
}

Mutation example with ArgsType

import { Arg, Args, Mutation, Query, Resolver } from "type-graphql";
import { UserSchema as User } from "../../schema/UserSchema";
import { CreateNewUserArgs } from "./types";

@Resolver(() => User)
export class UserResolver {
  
  @Mutation(() => User)
  async createUser(
    @Args()
    { firstName, lastName, email, password, phoneNumber }: CreateNewUserArgs
  ) {
    console.log(
      "firstName ",
      firstName,
      lastName,
      email,
      password,
      phoneNumber
    );
  }
}

Relations in type-graphql on the schema level

Relations example on the schema level. You can pass the arrow function in @Field() decorator to define a relation between schemas

Example

Add the below code at the end of the UserSchema

@Field((type) => [Product], { nullable: true })
products?: Product[];

Complete ProductSchema code with relation

@ObjectType()
export class ProductSchema {
  @Field()
  id: number;
  @Field()
  createdAt: Date;
  @Field()
  updatedAt: Date;
  @Field()
  productPic: string;
  @Field()
  productDescription: string;
  @Field()
  price: number;
  @Field(() => User)
  user: User;
}

Relations in type-graphql on resolver level

Just like query and mutation, you can define FieldResolver at the top of the function with return type and optional param

UserResolver example

@FieldResolver(() => [Product], { nullable: true })
async products(@Root() user: User) {
return [];
}

Complete type-graphql code link

GitHub – mahmedyoutube/typeorm-with-typegraphql at typegraphql-code

Conclusion

In short, in this article, we learn about type-graphql setup and relations between graph nodes with code examples.

If you still have a question you can reach out to me on LinkedIn. I will try my best to answer your question.

Thank you for reading my article