Rahman Fadhil I'm a full-stack developer, instructor, speaker, content creator, and eternal learner from Indonesia. I wrote my first "Hello world" app when I was 10 and I love to help people reach their dreams through programming.

How to build a GraphQL API with TypeGraphQL and TypeORM

8 min read 2271

How to build a GraphQL API with TypeGraphQL and TypeORM

GraphQL’s popularity is constantly growing, and it’s no mystery as to why: it’s a great tool that solves many common problems developers encounter with RESTful APIs. GraphQL allows us to easily fetch data relations, but it also prevents us from overfetching that data. Put simply, GraphQL improves the development experience and makes frontend apps faster.

Despite its many advantages, however, building a GraphQL API can occasionally present challenges. How can we minimize the headaches we encounter in our projects?

First, we need to have a schema that defines our types, mutations, and queries with SDL. Then, we need to write the resolvers that will resolve the values for our schema. We also need to define our ORM models that represent the data in our database. The fields we define in our models need to conform to our schema or it won’t work.

The main issue with this approach is that it’s difficult to maintain. If we want to change a field in our data, we need to change the database model class and GraphQL schema and adjust the type interface (if using TypeScript). But in this tutorial, I’m going to show you an enjoyable way to build a GraphQL API with TypeGraphQL and TypeORM.

TypeGraphQL is a framework for building GraphQL APIs with Node.js and TypeScript. The main purpose of this tool is to let us define our schema directly from our TypeScript code. TypeORM, on the other hand, is a TypeScript library that allows us to interact with SQL databases. With these tools combined, we can build a type-safe GraphQL API without the frustrations that usually come with such a project.

Without further ado, let’s walk through how to build a GraphQL API with TypeGraphQL and TypeORM that can manage books data with CRUD functionalities.

Prerequisites

Before you get started, make sure that you:

  1. Understand JavaScript
  2. Have a general understanding of Node.js and NPM
  3. Have basic knowledge of TypeScript

Getting started

We’ll begin by initializing a new Node.js project.

mkdir learn-typegraphql
npm init -y

Next, we’ll install some dependencies.

npm install apollo-server type-graphql typeorm reflect-metadata

Here we are installing:

We made a custom demo for .
No really. Click here to check it out.

  • Apollo Server to build and run our GraphQL server
  • TypeGraphQL to generate our schema from TypeScript classes
  • TypeORM to interact with our SQL database
  • reflect-metadata to work with TypeScript decorators

In addition, we need to install some development dependencies.

npm install -D typescript ts-node nodemon

This script will install:

  1. TypeScript to compile our code to plain JavaScript
  2. ts-node to run our server in development environment
  3. nodemon to automatically restart the server whenever we make changes to the code

Now, to make our job a bit easier, let’s define the NPM start scripts in package.json.

{
  // ...
  "scripts": {
    "start": "nodemon -w src --ext ts --exec ts-node src/index.ts"
  }
}

Next, create a tsconfig.json file. This file contains our TypeScript configurations, since we will use some TypeScript features that are currently still experimental, yet stable enough for our purposes.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false
  }
}

Make sure the experimentalDecorators and emitDecoratorMetadata are set to true.

For reference, I published the entire source code of this project to my GitHub. Feel free to poke around or clone it onto your computer.

git clone https://github.com/rahmanfadhil/learn-typegraphql.git

Setting up a GraphQL server

It’s time to start working on our server API. Let’s create a new file called index.ts inside the src folder.

// src/index.ts

import "reflect-metadata";
import { createConnection } from "typeorm";
import { ApolloServer } from "apollo-server";

async function main() {
  const connection = await createConnection()
  const schema = await buildSchema()
  const server = new ApolloServer({ schema })
  await server.listen(4000)
  console.log("Server has started!")
}

In this file, we can write a function called start. This function makes it easier to initialize every single library that we use in this project. In this function, we’ll first create a new connection to our database with the createConnection function provided by TypeORM.

Next, we’ll generate our GraphQL schema with the buildSchema method by TypeGraphQL. This will take all our resolvers and generate an executable GraphQL schema we can use inside our Apollo Server. These resolvers are a little bit different, which we’ll discuss later in this tutorial.

The reflect-metadata package we imported at the top is a helper library that extends the functionality of TypeScript decorators. This package is required to use TypeORM and TypeGraphQL.

Finally, we’ll initialize our Apollo Server, pass our schema, and start it in port 4000 (or any other port you want).

Database configuration

Remember when we created a database connection with TypeORM? Before we do anything else, we need to define a database configuration to tell TypeORM which kind of database we plan to use and how to access it. There are several ways to do this; personally, I like to create the configuration inside the ormconfig.json file.

Currently, TypeORM supports nine types of SQL databases, including popular ones such as MySQL and PostgreSQL. You can use any database you want, but for the sake of simplicity, I’m going to use SQLite — the smallest implementation of SQL database engine that is very easy to get started. To use this database, we must first install the driver for Node.js.

npm install sqlite3

Now, we can add the ormconfig.json file into our project.

{
  "type": "sqlite",
  "database": "./db.sqlite3",
  "entities": ["./src/models/*.ts"],
  "synchronize": true
}

Resolvers

To build our GraphQL resolvers, we’ll first define the mutations, queries, and other object types in our schema with GraphQL schema language. Then, we’ll define the resolvers in our JavaScript code to resolve the values of our schema.

The resolvers are usually a collection of functions that are mapped into a single object, and it has to match with the schema we defined earlier. This approach seems very complicated because we need to define both the schema and the resolvers in a separate place.

With TypeGraphQL, however, we don’t need to explicitly write the schema. Instead, we define our resolvers with TypeScript classes and decorators, and TypeGraphQL will generate the schema for us.

We can use the code below to define our resolvers with TypeGraphQL.

// src/resolvers/BookResolver.ts

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

@Resolver()
export class BookResolver {
  @Query(() => String)
  hello() {
    return "world";
  }
}

Here we created a class called BookResolver and decorated it with the Resolver decorator by TypeGraphQL. This enables us to place all our resolvers in this class as a method. We also want to make sure we decorate the method with either Query or Mutation and pass the return type on the first parameter.

So far, we just have a hello query that will return a string. Later, we will implement full CRUD to our database with TypeORM.

Now we need to register our resolver in src/index.ts.

import "reflect-metadata";
import { createConnection } from "typeorm";
import { ApolloServer } from "apollo-server";
import { BookResolver } from "./resolvers/BookResolver.ts"; // add this

async function main() {
  const connection = await createConnection()
  const schema = await buildSchema({
    resolvers: [BookResolver] // add this
  })
  const server = new ApolloServer({ schema })
  await server.listen(4000)
  console.log("Server has started!")
}

That’s it! To make sure everything is set up properly, let’s try to run our server by running npm start on the terminal and opening localhost:4000 in the browser.

Building a Resolver in GraphQL

Models

Now that our server is up and running, the next step is to define our models.

A model is essentially a class that allows us to interact with a specific table in our database. With TypeORM, we can define our database models with classes and decorators, just like our resolvers. And because we’re trying to build a bookstore API, let’s create a model that represents our books.

// src/models/Book.ts

import { Entity, BaseEntity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class Book extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: string;

  @Column()
  title: string;

  @Column()
  author: string;

  @Column({ default: false })
  isPublished: boolean;
}

A TypeORM model is essentially a plain TypeScript class that is decorated with Entity. This class contains properties that represent the fields of our table in the database. You can read more about it in the TypeORM official documentation.

This class extends the BaseEntity class, which contains useful methods to access our books table.

Object types

Since we are building a GraphQL API, we also need to define our object types. In GraphQL, every query and mutation returns an object, whether it’s a boolean, string, or a custom object we define ourselves. Just like our models, we can simply define our object types by using classes and decorators.

Here is where the magic happens. We can combine both TypeGraphQL and TypeORM decorators in a single TypeScript class. In that way, we can have a class that represents both GraphQL object type, as well as the database model. The code should look something like this:

// src/models/Book.ts

import { Entity, BaseEntity, PrimaryGeneratedColumn, Column } from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";

@Entity()
@ObjectType()
export class Book extends BaseEntity {
  @Field(() => ID)
  @PrimaryGeneratedColumn()
  id: string;

  @Field(() => String)
  @Column()
  title: string;

  @Field(() => String)
  @Column()
  author: string;

  @Field(() => Boolean)
  @Column({ default: false })
  isPublished: boolean;
}

This makes our code a lot more efficient because we define a single data type in one place, which should help reduce errors caused by property inconsistency.

Let’s say we want to update the isPublished property to published. Traditionally, when using the default GraphQL schema language, we’d need to define our data type in both the database model and the GraphQL schema. However, by using these decorators, we can simply update the property in our class to update both the schema and the model.

Database CRUD

After we create our database models, let’s go back to our resolvers and implement a query that returns all our books.

import { Resolver, Query } from "type-graphql";
import { Book } from "../models/Book";

@Resolver()
class BookResolver {
  @Query(() => [Book])
  books() {
    return Book.find()
  }
}

We’ll create the books method inside our resolver class and decorate it with Query. To specify the return type of our query, we need to pass it inside the Query decorator argument, which, in this case, is an array of books. Inside this method, we fetch our book with the find method from our model.

Now let’s go back to our playground and test this query.

Database CRUD Query in GraphQL

It returns an empty array, which means we’ve yet to create any books. Let’s do so by creating a mutation.

@Mutation(() => Book)
async createBook(@Arg("data") data: CreateBookInput) {
  const book = Book.create(data);
  await book.save();
  return book;
}

Here, we’re creating a createBook method that will return a book type. In this method, we initialize a new instance of Book, save it to the database with the save method, and return it. This method requires data as a parameter. To obtain data from users, we can build an input type to specify what fields are necessary for this mutation.

Let’s create an input to create a new book.

// src/inputs/CreateBookInput.ts

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

@InputType()
export class CreateBookInput {
  @Field()
  title: string;

  @Field()
  author: string;
}

The input type class is similar to our object type. The only difference is that we decorated the class with InputType. Also, the only fields that are necessary for creating a book are title and author, because the id is auto-generated by the database and isPublished field has a default value.

Let’s test it out!

Creating a New Book in GraphQL

Next, we’ll create a new query to fetch an individual book.

@Query(() => Book)
book(@Arg("id") id: string) {
  return Book.findOne({ where: { id } });
}

Let’s try this query:

Creating a New Query to Fetch a Book in GraphQL

So far, so good!

Now it’s time to add the update operation.

@Mutation(() => Book)
async updateBook(@Arg("id") id: string, @Arg("data") data: UpdateBookInput) {
  const book = await Book.findOne({ where: { id } });
  if (!book) throw new Error("Book not found!");
  Object.assign(book, data);
  await book.save();
  return book;
}

In the updateBook method, we need the id of the book we want to update as well as the user input, which we will create later. First, we’ll find the book, if it exists. Then, we’ll update the properties defined in the data parameter. Finally, we’ll save all changes to the database and return the updated book data to the user.

Below we define the input for updating a book.

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

@InputType()
export class UpdateBookInput {
  @Field({ nullable: true })
  title?: string;

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

  @Field({ nullable: true })
  isPublished?: boolean;
}

The input is very similar to our CreateBookInput class. However, all these properties are optional, which means the user doesn’t have to fill all the properties of the book.

Updating a Book in GraphQL

The last step is to implement the delete book feature.

@Mutation(() => Boolean)
async deleteBook(@Arg("id") id: string) {
  const book = await Book.findOne({ where: { id } });
  if (!book) throw new Error("Book not found!");
  await book.remove();
  return true;
}

The method is pretty straightforward. We find that book from the given id, remove it from the database with remove function, and return true for the result.

Removing a Book in GraphQL

Conclusion

TypeGraphQL can help solve many of the issues developers encounter when building GraphQL APIs, especially with TypeScript. Not only does it offer a cleaner and safer way to build GraphQL APIs, but it also prevents us from repeating the same tasks over and over. It’s even more useful if you use TypeORM because it takes the same approach to defining your database models. These tools are proven to get the job done and should be a top consideration for your next big project.

Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Rahman Fadhil I'm a full-stack developer, instructor, speaker, content creator, and eternal learner from Indonesia. I wrote my first "Hello world" app when I was 10 and I love to help people reach their dreams through programming.

10 Replies to “How to build a GraphQL API with TypeGraphQL and…”

  1. Any chance you could cover (or update this) to also show how to use typeorm and type-graphql to create an interface (i.e. interface User types teacher / student)?

  2. Good article. Looks extremely promising as a way to use GraphQL with TypeORM. How does this work with migrations? And, have you created any type of system using this approach with a large number of tables with relationships between them? I’m wondering how this will perform when there are 400+ tables.

  3. Thanks!

    If you’re dealing with a large number of tables, I suggest you take a look at DataLoader. You can use it alongside with TypeGraphQL and TypeORM. It allows you to gather every table relation query that happens in a single request and run them at the same time.

  4. I cannot for the life of me get this to build in Typescript. Dev works fine, but trying to run a built output always comes back with `import { Entity, BaseEntity, PrimaryGeneratedColumn, Column } from “typeorm”;`

  5. Thank you for this article.
    I like the simplicity of typeorm + type-graphql for basic CRUD operation.

    But it’s difficult to find real life examples with relationships (one to many, many to one, many to many)

    Ben Awad released a video, with data loader, but honestly it’s seems to be a pain in the a**

  6. Very nice article. I like the conventions of a single model/entity with the decorators for both TypeORM and GraphQL. The more conventional and repeatable the mechanism – the more it becomes a candidate for code generation and/or scaffolding via a CLI (i.e., Angular Schematics).

    I’m just getting started with this technology stack – however, the maturity of tools and process make it a strong candidate for enterprise applications. Ping me on twitter @Angularlicious, I’d love to continue the conversation…on my podcast, perhaps? Let me know. Thanks!

  7. Nice hint to combine Entities and type-graphql fields and type-orm entities. However I can see potential issues that could enforce a separation between the two type of objects. What if you have different database schema than what you want GQL queries structure? What if the database fields change but you don’t want to break the GQL interface?

Leave a Reply