Define Graphql Schema With Typegraphql In Typescript GraphQL Typescript

Nov 5th, 2021 - written by Kimserey with .

Last week we looked at how to setup an Apollo server to serve graphql requests. We went through the setup by defining a TemplateStringArray for the schema and defining functions in an object map for the resolvers. In this week post, we will look at how to simplify this two areas by leveraging Typescript features using typegraphql package.

Installation

We start from the Apollo server created in last week post - Get Started With Apollo Server. From there we can add our new dependencies:

1
npm i class-validator type-graphql reflect-metadata

class-validator is a dependency of typegraphl and reflect-metadata is a shim required for type reflection. We also import reflect-metadata at the top of our entrypoint index.ts:

1
import "reflect-metadata";

Then we make sure that our tsconfig.json file has the following settings:

1
2
3
4
5
6
7
8
9
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "lib": ["es2018", "esnext.asynciterable"],
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Those settings must be set correctly for typegraphql to work properly.

Define Schema

Now that we have the package installed properly, we can start by moving our schema to a typed schema rather than a string. This is what we started with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const typeDefs = gql`
  type Person {
    id: ID!
    name: String!
  }

  type Book {
    title: String
    author: Person
  }

  type Query {
    books: [Book]
  }
`;

We can see that we have three types, Person, Book and Query. We will leave Query aside first and define Person and Book:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@ObjectType()
class Person {
  @Field(() => ID)
  id: string;

  @Field(() => ID)
  name: string;
}

@ObjectType()
class Book {
  @Field(() => String)
  title: string;

  @Field(() => Person, { nullable: true })
  author: Person;
}

As we can see, we are able to create types that mirror our schema. We use ObjectType decorator to indicate to typegraphql that this class is a type for graphql and Field to specify the field and the type of the field. ID is a special value from typegraphql which indicates an opaque value.

Define Resolver

Next we can update our resolvers; which previously looked as such:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const resolvers = {
  Query: {
    books: () => [
      {
        title: "Made of Wolves",
        authorId: "1",
      },
      {
        title: "The Visitor in the City",
        authorId: "2",
      },
    ],
  },
  Book: {
    author: (parent: any) => {
      return {
        id: parent.authorId,
        name: parent.authorId == "1" ? "James Carter" : "Arthur Novotic",
      };
    },
  },
};

We had a Query resolver with books attribute and a Book resolver with author attribute. Those can be defined with typegraphql as such:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Resolver((_of) => Book)
class BookResolver {
  @FieldResolver()
  author(@Root() book: Book): Person {
    return {
      id: book.authorId,
      name: book.authorId == "1" ? "James Carter" : "Arthur Novotic",
    };
  }

  @Query((_returns) => [Book])
  books(): Book[] {
    return [
      {
        title: "Made of Wolves",
        authorId: "1",
      },
      {
        title: "The Visitor in the City",
        authorId: "2",
      },
    ];
  }
}

We can see that we use @Resolver decorator to define a class as the book resolver. The query is then defined using the @Query decorator and the Book.author field is defined using the @FieldResolver decorator. @Root represents the first parent argument given to the resolver, the other argument can be retrieved using @Ctx or @Arg.

Build Schema

Lastly we can build the schema using buildSchema which we import from type-graphql:

1
2
3
4
5
6
7
8
9
10
11
async function main() {
  const schema = await buildSchema({
    resolvers: [BookResolver],
  });

  const server = new ApolloServer({ schema });
  await server.listen(4000);
  console.log("Server started on http://localhost:4000");
}

main();

buildSchema will translate all the queries and mutations together with associated types link into the resolvers. And once we start our server, we can see our graphql schema as expected! And that concludes today’s post!

Conclusion

Today we saw how we could create a graphql schema in Typescript with classes using type-graphql. We started from last week project which was implementing barebone graphql schema and resolvers with Apollo Server, and translated the schema into Typescript classes and resolvers into resolver classes. We then finished the post by building the schema and testing the resulting schema. I hope you liked this post and I see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.