How to implement a GraphQL API using TypeScript and TypeORM

In the past few years, GraphQL has proven to be one the most dominant stacks in software development due to multiple common problems it solves such as data over fetching and under fetching. However developing a GraphQL API in Node.js with TypeScript is sometimes a bit of a pain.

Why ?

Let’s take a look at the steps we usually have to take.

First, we create all the schema types in SDL. We also create our data models using ORM classes, which represent our database entities. Then we start to write resolvers for our queries, mutations and fields. This forces us, however, to begin with creating TypeScript interfaces for all arguments and inputs and/or object types. After that, we can actually implement the resolvers.

The main drawback with this approach is code redundancy which makes the maintainability of the codes difficult. In order to add a new field to our entity, we have to jump through all the files, modify the entity class, then modify the schema, and finally update the interface. The same goes with inputs or arguments, it’s easy to forget to update one of them or make a mistake with a type. But in this tutorial, with the help of TypeGraphQL and TypeORM, we are going to make a maintainable GraphQL API.

TypeGraphQL is a framework for building GraphQL APIs with Node.js and TypeScript. This tool actually addresses the problem mentioned above by allowing us to use TypeScript classes and decorators to create the schema, types, and resolvers of our API.

TypeORM is an object relations management library made in TypeScript that helps us to interact with databases. Having these tools assembled, we can definitely make a type-safe and maintainable GraphQL API.

Prerequisites

This tutorial assumes that you have some experience using the following tools:

  • TypeScript

That was enough of theories, let get our hands dirty 😎

Project Setup

Let’s initialize a new Node.js project.

mkdir graph-ts-apinpm init -y

Next, we install the dependencies needed for this tutorial.

npm install express apollo-server-express graphql reflect-metadata type-graphql class-validator typedi typeormnpm install cross-env pg dotenv nodemon ts-node eslint prettier typescript --save-dev

Here’s what each of the installed packages do:

  • Express is a web framework for Node.js used for server creation

Project Structure

graph-ts-api
├─ src
│ ├─ database
│ │ ├─ entity
│ │ │ └─ Movie.ts
│ │ ├─ index.ts
│ │ ├─ migrations
│ │ │ └─ 1615219775175-MovieTable.ts
│ │ └─ services
│ │ └─ movieService.ts
│ ├─ index.ts
│ ├─ resolvers
│ │ └─ movie.ts
│ └─ schema
│ └─ movie.ts
└─ tsconfig.json
├─ .eslintignore
├─ .eslintrc.json
├─ .gitignore
├─ .prettierignore
├─ .prettierrc
├─ ormconfig.ts
├─ package.json
├─ README.md
├─ schema.gql

Since all required dependencies are installed, let’s add some few commands in package.json for running the server in development environment, generating the build folder and executing the server in production environment.

"scripts": {    
"build": "rimraf ./build && tsc",
"start": "cross-env NODE_ENV=production ts-node build/index.js", "dev": "nodemon src/index.ts"
}

Let us now create tsconfig.json file and add few configurations that we shall need in the project.

In order for us to use decorator experimentalDecorators and emitDecoratorMetadata must be set to true. For more details about tsconfig, follow this link https://www.typescriptlang.org/tsconfig.

Prettier and Eslint Configuration

Create a .eslintrc file in the project root and use the following starter configuration:

And add the following linting command to package.json:

"lint": "eslint . --ext .ts",

Create again another file in the project root called .prettierrc containing the following configurations:

Database Setup

Now we need to connect our app to the database. TypeORM offers multiple ways to connect an app to the database and it does support many databases, SQL and NoSQL. But for the sake of this tutorial we are going to use PostgresQL as mentioned above and all our database configuration will be defined inside the ormconfig.ts file that will be created in the root directory of the project.

As you can see, we are loading all the db credentials from the env for security reasons. Initially the NODE_ENV is set to development, so we are performing the checking in order to tell the ormconfig to point to the build folder when the app is in production mode and to src when it is in development mode, another checking is executed to determine the db to use depending on the NODE_ENV and with the synchronize property given we don’t want to generate the database schema on every application launch when the app is in production mode.

Let’s add an index.ts inside the database folder which is going to contain our db initialization function.

Migration Setup

We will be using TypeORM CLI for generating and running our migration. So let’s add migration commands inside package.json

"typeorm:cli": "ts-node ./node_modules/typeorm/cli -f ./ormconfig.ts","create-table": "npm run typeorm:cli -- migration:run","drop-table": "npm run typeorm:cli -- migration:revert"

Before generating our migration file, let’s first define the movie entity/model

TypeORM provides a large set of decorator functions that we can use to define the requirement about various fields of a table, for instance in the snippet code above we are using PrimaryGeneratedColumn() decorator to mean that id field must be primary key and with actors fields we are explicitly telling that it can take an array of string and with other fields we have used the name property to specify the field name that will be used by the table. And we also extend the BaseEntity class so that we can empower our model class with useful methods that can be leveraged when performing CRUD operations.

Our model class is now in place, let’s generate our migration file by running the following command

npm run typeorm:cli -- migration:create -n MovieTable

After executing the above command, a migration file will be generated inside the migrations folder under database folder having this content:

With QueryRunner, we can either build the query by hand or use the migration API. let’s use the first approach for dropping table query and the second one for creating table query, here is how it looks after adding the queries:

Now when we can run this command to create the movie table

npm run create-table

or execute the following for dropping it

npm run drop-table

Finally let’s add the movie service file that encapsulates the CRUD implementation.

Here, the CreateMovieInput and UpdateMovieInput contain the shape definition of the input data expected for adding and editing the movie, we will talk more about them in the schema definition section. We also call the Service() decorator to tell TypeGraphQL that MovieService class can be injected as a dependency.

Adding Schema

Under the schema folder we create movie.ts file and add the following contents:

On the first sight the syntax might look a little bit strange, but it’s actually simple to understand. It’s just TypeScript decorators and classes.

The ObjectType() decorator create a new object or schema, the Movie class reflects the shape of a Movie object and the CreateMovieInput & UpdateMovieInput describe the required data expected for adding and editing a movie. Something to note down is whenever a field is of type array, we have to explicitly specify it inside the Field() decorator as you can see with the actors field, the Length() decorator which is provided by class-validator module is used to validate the title and description field.

Adding Resolver

Let’s create movies.ts file under resolvers folder

Before going further, we should know TypeGraphQL puts the GraphQL query or mutation in the resolvers. The name of the method will be used as an endpoint when querying or mutating data.

Here, we use the Service() decorator in order to inject the MovieService as a dependency to the resolver class, and the Resolver() decorator to create a new GraphQL resolver that returns a Movie. Next, we build a GraphQL query to retrieve all Movies and one Movie.

After that, we define three mutations, the addMovie and updateMovie mutation expect respectively input data that can match the CreateMovieInput and UpdateMovieInput definition, while the deleteMovie mutation only needs the id argument to perform the deletion.

Server Creation

Let’s create an index.ts file under the src folder.

We import reflect-metadata package so that we can extends the functionality of TypeScript decorators. Note that while using TypeORM and TypeGraphQL, that package is paramount.

As you can see, we import the initializeDB function in order to get connected to our db. We pass the MovieResolver to the resolver property of the buildSchema method. So that, TypeGraphQL can build a new GraphQL Schema based on that resolver class.

We provide buildSchema method with a container too, which allows dependency injection of our service class. The property emitSchemaFile is set true to allow TypeGraphQL to generate a schema.gql file at build-time.

Let’s start our server in development mode by running this command:

npm run dev

You should see the following output:

Time to test our different endpoints, let’s visithttp://localhost:5000/graphql

Add Movie

Get Movies

Get Movie

Update Movie

Delete Movie

Conclusion

Combining TypeORM and TypeGraphQL can drastically fasten the development time and improve the maintainability & readability of the codes. Using these tools can be useful when planning to build a GraphQL API with TypeScript.

You can find the full codes of this article by clicking here.

Check out these resources for more details about this tutorial:

We have reached the end of our tutorial, if you have found it useful, feel free to clap or comment your thoughts. Any suggestions or feedback will be welcomed. You can also connect with me on Twitter, LinkedIn, GitHub.

Mobile Application & Backend Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store