How to use TypeScript with GraphQL

Ibrahima Ndaw - Jan 25 '21 - - Dev Community

GraphQL is a powerful query language that allows you to build flexible APIs. It lets you define a type system for your data, so when you execute a query, it returns only the data you need.

GraphQL can offer a better developer experience when used with TypeScript because they are both typed language. TypeScript is a typed superset of JavaScript that extends it by adding types. So, using these technologies together will certainly help you to build predictable and strongly-types APIs.

In this tutorial, I will first explain why combining such technologies, and then show you how to use TypeScript with GraphQL by building an API from scratch using TypeGraphQL.

Prerequisites

This tutorial assumes that you have some experience using TypeScript, particularly TypeScript classes and decorators. Knowledge of GraphQL will come in handy but is not mandatory.

In this guide, we will be using TypeGraphQL, which is a modern framework for building GraphQL API using Node.js and TypeScript.

Why use TypeScript with GraphQL

TypeScript is a popular programming language developed and maintained by Microsoft. It is a superset of JavaScript that uses static type-checking to make your code predictable.

Over the years, TypeScript has proven to be a valuable language for large codebases. TypeScript enhances code quality with its types, which adds many benefits, such as robustness, understandability, and predictability.

GraphQL solves the problem of over-fetching or under-fetching APIs. It provides one single endpoint for all requests using a Post method to get exactly the data you need, nothing more and nothing less. In this way, GraphQL makes your queries flexible, and your API readable and easy to learn.

TypeScript and GraphQL both rely on types to make your code understandable. However, GraphQL types can only be defined in a GraphQL schema using the method buildSchema or a file with .gql extension. The GraphQL types are not supported on GraphQL resolvers because resolvers are just regular JavaScript functions, not GraphQL code. TypeScript solves this issue because, as we mentioned earlier, it’s a superset of JavaScript. So, It can set types on the GraphQL resolvers. This is the reason why using TypeScript along with GraphQL makes sense.

GraphQL handles the types for the GraphQL schemas, and TypeScript sets the types on the GraphQL resolvers. However, because you are handling multiple languages, building strongly-typed APIs using Node.js, GraphQL, and TypeScript can be a challenge to maintain.

Maintaining consistency between your schema and resolvers is what TypeGraphQL intends to solve. TypeGraphQL allows you to use TypeScript classes and decorators to create the schema, types, and resolvers of your API. It uses TypeScript to build the entire GraphQL API.

https://paper-attachments.dropbox.com/s_FE1E1F2FD073B6FBA4EBF25A186A644F8A1660C235BABE83F3933C922FDEEEEB_1600278411563_illustration.png

illustration

So far, we have learned why pairing TypeScript with GraphQL can be useful and why TypeGraphQL is handy for building and maintaining GraphQL APIs that use TypeScript.

Without further ado, let’s dive into the practice part and build up the GraphQL API using TypeGraphQL.

Setting up

To use TypeScript and GraphQL, we first need to create a new Node.js app.

Open your command-line interface (CLI) and run this command:

yarn init
Enter fullscreen mode Exit fullscreen mode

Or for npm

npm init
Enter fullscreen mode Exit fullscreen mode

You’ll need to respond to a few configuration questions which will emit a package.json file. Next, install the dependencies needed for this tutorial.

yarn add express apollo-server-express graphql reflect-metadata type-graphql class-validator
Enter fullscreen mode Exit fullscreen mode

For npm

npm install express apollo-server-express graphql reflect-metadata type-graphql class-validator
Enter fullscreen mode Exit fullscreen mode

We will break these packages down later and explain what they do. For now, let’s install their types so TypeScript can understand the libraries.

yarn add -D @types/express @types/node nodemon
Enter fullscreen mode Exit fullscreen mode

Or

npm install -D @types/express @types/node nodemon
Enter fullscreen mode Exit fullscreen mode

Note that we installed nodemon as well to enable live-reloading whenever a change occurs.

Here’s what each of the installed libraries do:

  • express is a minimalist web framework for Node.js
  • apollo-server-express is a middleware that allows using express in an Apollo GraphQL server.
  • reflect-metadata enables TypeScript decorators to add the ability to augment a class and its members when the class is defined. It’s a dependency of TypeGraphQL.
  • class-validator allows TypeGraphQL to use decorator and non-decorator based validation.

Next, we need to structure the project as follows:

src
| ├── resolvers
| |  └── todoResolver.ts
| └── schemas
| |  └── Todo.ts
| └── index.ts
├── tsconfig.json
├── package.json
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

Here, there are four files to underline:

  • The entry point of the server (index.ts).
  • The schemas folder that contains the GraphQL Schema of the project.
  • The resolvers folder that holds the resolvers of the API.
  • The tsconfig.json file tells to TypeScript how to compile the code.

With this in place, we can now add a script to start the server in the package.json file.

"scripts": {
  "start": "nodemon --exec ts-node src/index.ts"
}
Enter fullscreen mode Exit fullscreen mode

This script will start the server using nodemon. And whenever our code is updated, it will restart.

Let’s now configure the tsconfig.json.

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}
Enter fullscreen mode Exit fullscreen mode

These two properties should be set to true to be able to use TypeScript decorators in the project.

We can now build a GraphQL Schema for the API.

Build the GraphQL Schema

TypeGraphQL allows you to build a schema using TypeScript classes and decorators. It’s just syntactic sugar because under the hood TypeGraphQL will still generate regular GraphQL code. We will see the code generated later - for now, let’s create the schema.

  • schemas/Todo.ts
import { Field, ObjectType, InputType } from type-graphql

@ObjectType() export class Todo {
  @Field() id: number
  @Field() title: string
  @Field() description: string
  @Field() status: boolean
}

@InputType() export class TodoInput implements Partial {
  @Field() title: string
  @Field() description: string
}
Enter fullscreen mode Exit fullscreen mode

At the first the syntax might look weird, however, it’s relatively simple to understand. It’s just TypeScript decorators and classes.

Here, the @ObjectType() provided by TypeGraphQL enables creating a new object or schema. The Todo class reflects the shape of a Todo object, and the TodoInput defines the expected data for adding a new Todo.

Now, let’s write the same code using GraphQL.

type Todo {
  id: ID!
  title: String!
  description: String!
  status: Boolean!
}

input TodoInput {
  title: String!
  description: String!
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the logic is the same. The only difference is that here, we don’t use TypeScript.

Now we’re ready to create our GraphQL resolvers.

Create the GraphQL resolver

Unlike GraphQL, TypeGraphQL puts the GraphQL query or mutation in the resolvers. The name of the function will be used as an endpoint when querying or mutating data.

  • resolvers/todoResolver.ts
import { Query, Resolver, Mutation, Arg } from type-graphql;
import { Todo, TodoInput } from ../schemas/Todo;

@Resolver((of) => Todo) export class TodoResolver { 
    private todos: Todo[] = []

    @Query((returns) => [Todo], { nullable: true })
    async getTodos(): Promise<Todo[]> {
        return await this.todos
    }

    @Mutation((returns) => Todo)
    async addTodo(
        @Arg('todoInput') { title, description }: TodoInput
    ): Promise<Todo> {
        const todo = {
            id: Math.random(), // not really unique
            title,
            description,
            status: false,
        }

        await this.todos.push(todo)
        return todo
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we use the Resolver decorator to create a new GraphQL resolver that returns a Todo. Next, we build a GraphQL query to fetch all Todos.

After that, we define a mutation query that expects a title, and a description to add a new Todo on the array of data.

By the way, you don’t need to use async/await here, because this won’t take time to complete. But, I add it here for reference when you need to deal with a real server.

Let’s now convert the code to GraphQL.

type Mutation {
  addTodo(todoInput: TodoInput!): Todo!
}

type Query {
  getTodos: [Todo!]
}
Enter fullscreen mode Exit fullscreen mode

With this in place, we can build the server that uses the schema and resolver we’ve just created.

Create the Server

  • src/index.ts
import reflect-metadata;
import { ApolloServer } from apollo-server-express;
import * as Express from express import { buildSchema } from type-graphql;
import { TodoResolver } from ./resolvers/todoResolver;

async function main() { const schema = await buildSchema({ resolvers: [TodoResolver], emitSchemaFile: true, })

    const app = Express()

    const server = new ApolloServer({
        schema,
    })

    server.applyMiddleware({ app })

    app.listen(4000, () =>
        console.log('Server is running on http://localhost:4000/graphql')
    )

}

main()
Enter fullscreen mode Exit fullscreen mode

As you can see here, we import TodoResolver, which needs to be passed as a resolver to the buildSchema method. With that, TypeGraphQL can build a new GraphQL Schema based on the Todo resolver.

Next, we pass the schema object (it contains the GraphQL schemas and resolvers) to Apollo to create the server.

Setting the property emitSchemaFile: true allows TypeGraphQL to generate a schema.gql file at build-time.

Let’s check if the app works. Run the following command:

yarn start
Enter fullscreen mode Exit fullscreen mode

Or

npm start
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:4000/graphql, and then add this code block below to GraphQL Playground to create a new Todo.

mutation {
  addTodo(todoInput: { title: "Todo 1", description: "This is my todo" }) {
    title
    description
    status
  }
}
Enter fullscreen mode Exit fullscreen mode

The Todo object should be created successfully!

https://paper-attachments.dropbox.com/s_FE1E1F2FD073B6FBA4EBF25A186A644F8A1660C235BABE83F3933C922FDEEEEB_1599926790317_add-todo.PNG

todo-created

Now query for the newly created Todo using the following GraphQL query.

{
  getTodos {
    title
    description
    status
  }
}
Enter fullscreen mode Exit fullscreen mode

You should see that all Todos have been returned.

https://paper-attachments.dropbox.com/s_FE1E1F2FD073B6FBA4EBF25A186A644F8A1660C235BABE83F3933C922FDEEEEB_1599926830468_all-todos.PNG

all-todos

Great! Our app looks good.

We have now finished building a GraphQL API using TypeScript.

You can find the finished project in this Github repo

Thanks for reading

GraphQL in TakeShape

TakeShape provides a flexible GraphQL API to manage your content easily. It gives you the ability to immediately see how changes to your content model will impact your API using the API Explorer. You don’t have to build any backend on your own, everything is set up for you. TakeShape automatically generates a secure GraphQL API to expose all of your content and services.

Next steps

Check out these resources to dive deeper into the content of this tutorial:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .