Introduction to TypeGraphQL

While GraphQL has revolutionized how developers interact with data, managing its complexity in a Node.js environment can sometimes be cumbersome, particularly when using TypeScript. Let’s explore TypeGraphQL, a framework that aims to simplify the creation of GraphQL APIs in a Node.js and TypeScript setting.


This content originally appeared on Telerik Blogs and was authored by Hassan Djirdeh

GraphQL has changed how developers interact with data, but managing its complexity in Node.js can be a pain, especially with TypeScript. Let’s explore TypeGraphQL, a framework that aims to simplify the creation of GraphQL APIs in a Node.js and TypeScript setting.

In the modern landscape of web development, API design has become a cornerstone of application architecture. While GraphQL has revolutionized how developers interact with data, managing its complexity in a Node.js environment can sometimes be cumbersome, particularly when using TypeScript. This is where TypeGraphQL comes into play, a framework that aims to simplify the creation of GraphQL APIs in a Node.js setting.

In this article, we’ll delve into the fundamentals of TypeGraphQL, illustrating its benefits and usage.

Want to better understand what GraphQL is and how it differs from traditional REST APIs? Check out this article we’ve written before: GraphQL vs. REST—Which is Better for API Design?

TypeGraphQL

TypeGraphQL is a library designed to make building GraphQL APIs in TypeScript more efficient. It does this by leveraging decorators and TypeScript classes to define schemas and resolvers succinctly, allowing developers to write cleaner and more scalable code.

In a previous article we wrote on GraphQL Resolvers, we went through a simple example of fetching user data based on an ID. In this traditional setup, the GraphQL schema (SDL) and resolvers looked something like the following:

Schema:

type Query {
  user(id: ID!): User
}

type User {
  name: String!
  email: String!
}

Resolver:

const resolvers = {
  Query: {
    user: (obj, args, context, info) => {
      /*
        fetch the user data from the database using 
        the id from args
      */
      return database.getUserById(args.id);
    },
  },
};

The above features a schema and resolver for a user query. In the schema, a Query type allows fetching a User object by ID, which includes name and email fields. The resolver maps this query to a function that retrieves user data from a database using the provided ID, effectively bridging the GraphQL schema to the database.

Let’s see how this traditional approach translates into a TypeGraphQL framework, enhancing type safety and reducing boilerplate.

TypeGraphQL Schema and Resolver

First, we define the GraphQL types using TypeScript classes and decorators provided by TypeGraphQL, like ObjectType, Field and ID:

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

@ObjectType()
class User {
  @Field((type) => ID)
  id: string;

  @Field()
  name: string;

  @Field()
  email: string;
}

In the above example, we define the GraphQL object type for a User using TypeScript classes, which enhances type safety and maintainability. Each field of the User object is defined with a @Field decorator, which describes the type of data (such as ID, String, etc.) and whether it is nullable or not. This approach integrates the GraphQL schema directly into the TypeScript environment, so the data types used are consistent across the API.

Next, we define the resolver class which will handle the fetching of user data. We’ll use the @Resolver decorator from TypeGraphQL to bind our class to the specified GraphQL type, which in this case is User:

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

@Resolver((of) => User)
class UserResolver {
  @Query((returns) => User, { nullable: true })
  async user(@Arg("id") id: string): Promise<User | undefined> {
    // Use the database service to fetch user data by ID
    return await database.getUserById(id);
  }
}

In the above example, we implement a resolver class to handle queries for fetching user data. The @Resolver decorator indicates that this class will resolve fields for the User type. The @Query decorator is used to define a query operation within the GraphQL API. The user function takes an id argument marked with the @Arg decorator and returns a User object or undefined if no user is found with that ID. This setup not only simplifies the code but also ensures that each component of the API is strongly typed and adheres to the schema definitions.

To build the schema, we can use the buildSchema() function provided by TypeGraphQL.

import { buildSchema } from "type-graphql";
import { UserResolver } from "./UserResolver";

async function createSchema() {
  return await buildSchema({
    resolvers: [UserResolver],
  });
}

In the above example, buildSchema() is configured with UserResolver, integrating all the logic we defined earlier and creates an executable schema from our type and resolver definitions. Finally, we’ll need to actually run the async function:

import { buildSchema } from "type-graphql";
import { UserResolver } from "./UserResolver";

async function createSchema() {
  return await buildSchema({
    resolvers: [UserResolver],
  });
}

createSchema();

With the schema now built, the GraphQL endpoint can be created and served using tools like Apollo Server or graphql-yoga, showcasing how TypeGraphQL facilitates the integration of TypeScript with GraphQL. The shift from traditional GraphQL setup to TypeGraphQL introduces a more structured and scalable approach to building GraphQL APIs, leveraging the full power of TypeScript’s static typing system.

The above only touches the surface of what can be done with TypeGraphQL, and there’s a lot more to explore. Below, we’ll go through one core feature TypeGraphQL supports well—authorization.

Authorization

In TypeGraphQL, the @Authorized decorator is used to implement authorization controls directly within schema definitions. This powerful feature allows us to specify which roles can access specific fields or execute queries and mutations.

Let’s expand the previous User example to include authorization checks that only authorized users can access certain user details. First, we’ll modify the UserResolver to include methods that are protected with the @Authorized decorator, specifying which roles are allowed to access them.

import { Resolver, Query, Arg, Mutation, Authorized } from "type-graphql";
import { User, UserInput } from "./User";

@Resolver((of) => User)
class UserResolver {
  // Public query accessible by anyone
  @Query((returns) => User, { nullable: true })
  async user(@Arg("id") id: string): Promise<User | undefined> {
    return await database.getUserById(id);
  }

  // Restricted query only accessible by admins
  @Authorized("ADMIN")
  @Query((returns) => [User])
  async allUsers(): Promise<User[]> {
    return await database.getAllUsers();
  }

  // Mutation to update user, restricted to logged-in users
  @Authorized()
  @Mutation((returns) => User)
  async updateUser(@Arg("userData") userData: UserInput): Promise<User> {
    return await userService.update(userData);
  }
}

In the code example above, we enhanced the UserResolver by integrating role-based access control. Queries and mutations are protected using the @Authorized decorator, specifying role requirements directly in the GraphQL schema definition. This setup restricts access based on user roles, so that only appropriately authorized users can perform certain actions, such as fetching all users or updating user details.

Next, we need to create an auth checker function, the function that defines the logic to verify if the current user’s roles match the roles required by the @Authorized decorator.

import { AuthChecker } from "type-graphql";
import { Context } from "./Context";

export const customAuthChecker: AuthChecker<MyContext> = (
  { context },
  roles
) => {
  // Ensure there is a user in the context
  if (!context.user) return false;

  // If no specific roles are required, allow any authenticated user
  if (!roles.length) return true;

  // Check if the user’s roles include any of the required roles
  return roles.some((role) => context.user.roles.includes(role));
};

The above example auth checker function checks:

  1. If a user is present in the context.
  2. If no roles are specified, it allows access to any authenticated user.
  3. If specific roles are specified, it checks if the user has any of those roles.

Finally, we’ll need to integrate this auth checker function when we build our GraphQL schema with TypeGraphQL. This step involves registering the custom auth checker function with the schema.

import { buildSchema } from "type-graphql";
import { UserResolver } from "./UserResolver";
import { customAuthChecker } from "./auth/custom-auth-checker";

async function createSchema() {
  return await buildSchema({
    resolvers: [UserResolver],
    authChecker: customAuthChecker,
  });
}

By following the steps, we would have successfully integrated role-based authorization into our TypeGraphQL API. This setup not only secures our API but also makes it so that only authorized users can access sensitive operations and data.

Wrap-up

In this article, we’ve introduced some of the core concepts of what TypeGraphQL has to offer. Beyond the basics of defining schemas and resolvers, TypeGraphQL comes packed with features such as dependency injection, validation, inheritance and middleware, which enhance its capabilities. Furthermore, TypeGraphQL supports more advanced GraphQL capabilities like enums, subscriptions and directives.

TypeGraphQL’s integration of these capabilities into the TypeScript ecosystem allows developers to produce secure, scalable and maintainable APIs efficiently. For more details, be sure to check out the official TypeGraphQL documentation!


This content originally appeared on Telerik Blogs and was authored by Hassan Djirdeh


Print Share Comment Cite Upload Translate Updates
APA

Hassan Djirdeh | Sciencx (2024-07-26T09:14:09+00:00) Introduction to TypeGraphQL. Retrieved from https://www.scien.cx/2024/07/26/introduction-to-typegraphql/

MLA
" » Introduction to TypeGraphQL." Hassan Djirdeh | Sciencx - Friday July 26, 2024, https://www.scien.cx/2024/07/26/introduction-to-typegraphql/
HARVARD
Hassan Djirdeh | Sciencx Friday July 26, 2024 » Introduction to TypeGraphQL., viewed ,<https://www.scien.cx/2024/07/26/introduction-to-typegraphql/>
VANCOUVER
Hassan Djirdeh | Sciencx - » Introduction to TypeGraphQL. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/26/introduction-to-typegraphql/
CHICAGO
" » Introduction to TypeGraphQL." Hassan Djirdeh | Sciencx - Accessed . https://www.scien.cx/2024/07/26/introduction-to-typegraphql/
IEEE
" » Introduction to TypeGraphQL." Hassan Djirdeh | Sciencx [Online]. Available: https://www.scien.cx/2024/07/26/introduction-to-typegraphql/. [Accessed: ]
rf:citation
» Introduction to TypeGraphQL | Hassan Djirdeh | Sciencx | https://www.scien.cx/2024/07/26/introduction-to-typegraphql/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.