All Articles

Database to client type safety with Typescript, TypeORM, type-graphql and Apollo

The Problem

One of the hardest problems I find when building web applications is wiring up the frontend to the backend. I spend way more time getting the backend set up with the correct data model, data structures and defining the API and then when it comes to wiring up the frontend, trying to remember the schema and correct types that the API expects I always get it wrong.

Thankfully, with the right toolchain it is now possible to end-to-end type safety from your database, through your application code, and into the client by using a select number of tools for the job.

What is type safety and why is it important?

In a nutshell, type safety basically means that the types - be a string, a number, boolean etc - are defined and any data that doesn’t fit the type will be rejected thus preventing type errors.

In most applications the underlying types will be the schema of your database, with each field having a fixed type. It is in the layers above the database where translation and magic occurs that lead to most typing issues.

Types with GraphQL

Typically the largest translation layer is between the frontend and the backend. Be it a react app, a mobile app or some other client, talking back to the server requires the two speaking the same language.

These days my default is GraphQL which provides a rich, fully typed schema for all it’s operations. I won’t go into detail now on GQL as it is covered elsewhere in depth, but it is vital tool in achieving the typed-nirvana I’ve always dreamed of.

The Tools

Most of my applications now use the following stack to achieve end-to-end type safety and I never have to worry about checking the calls between the layers as everything is strongly typed.

  • typescript - it is in the name. Used both in the server side (node) and client (react in my case)
  • TypeORM - my ORM of choice for defining my data models and managing the interaction with the database
  • TypeGraphQL - produces a GraphQL API based on the typescripts classes via annotations
  • apollo-client - the defacto GraphQL client
  • GraphQL Code Generator - a magic tool for generating typescript types, code generation for GQL operations and more from any given GQL schema

Sticking them together

At a high level my process is as follows:

  1. Define a data model in TypeORM

    import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
    
    @Entity()
    export default class Post {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      title: string;
    
      @Column()
      body: string;
    }
  2. Define the GraphQL types, via TyepGraphQL annotations on the same class (thus linking the types)

    import { Field, ID, ObjectType } from "type-graphql";
    import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
    
    @ObjectType()
    @Entity()
    export default class Post {
      @Field()
      @PrimaryGeneratedColumn()
      id: number;
    
      @Field()
      @Column()
      title: string;
    
      @Field()
      @Column()
      body: string;
    }

    Note: For something simple I would use the same classes for my ORM data model and my GraphQL types but for more sophisticated apps I will split these up and map data between the two (still type safe due to typescript!)

  3. Create a resolver and define methods with a query annotation for TypeGraphQL to generate a GraphQL schema from

    import {
      Arg,
      Mutation,
      Query,
      Resolver,
      ResolverInterface,
    } from "type-graphql";
    import Post from "../entities/Post";
    
    @Resolver((of) => Post)
    export default class PostResolver implements ResolverInterface<Post> {
      @Query(() => [Post])
      async posts() {
        // in reality you would call your database via TypeORM - dummy content for now
        return [
          {
            id: 1,
            title: "My post",
            body: "Some content",
          },
        ];
      }
      @Query(() => Post, { nullable: true })
      async post(@Arg("id") id: number): Promise<Post | undefined> {
        // in reality you would call your database via TypeORM - dummy content for now
        return {
          id: 1,
          title: "My post",
          body: "Some content",
        };
      }
    }
  4. Create the GraphQL server itself

    import "reflect-metadata";
    import { ApolloServer } from "apollo-server";
    import { buildSchema } from "type-graphql";
    import { createConnection } from "typeorm";
    import PostResolver from "./resolvers/PostResolver";
    
    async function init() {
      // reads from ormconfig.json by default
      await createConnection();
    
      const schema = await buildSchema({
        resolvers: [PostResolver], //reference the resolver
      });
    
      // Create GraphQL server
      const server = new ApolloServer({
        schema,
        playground: true,
        tracing: true,
        introspection: true,
      });
    
      // Start the server
      await server.listen(8000);
      console.log(`Server is running on 8000`);
    }
    
    init();
  5. Switching the frontend, setup GraphQL Code Generator to read you schema and output TS types Install the CLI and set the config as:

    overwrite: true
    schema: "http://localhost:8000/graphql"
    documents: "src/**/*.graphql"
    generates:
    src/generated/graphql.tsx:
      plugins:
        - "typescript"
        - "typescript-operations"
        - "typescript-react-apollo"
      config:
        withHooks: true

    This will generate a graphql.tsx file which contains the generated type derived from your GraphQL schema as well as apollo-client queries and mutation methods (and hooks!)

  6. Now in your react code you can require in the generated hooks which will provide fully typed inputs and results to method calls which are driven by your GraphQL server, which is generated from your database model:

import React from "react";
import { useCreatePostMutation, usePostsQuery } from "../../generated/graphql";

interface Props {}

function Home(props: Props) {
  const posts = usePostsQuery(); //posts is now a Apollo client query result typed as Post

  return (
    <dive>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Body</th>
          </tr>
        </thead>
        <tbody>
          {posts.data &&
            posts.data.posts.map((post) => {
              return (
                <tr>
                  <td>{post.id}</td>
                  <td>{post.title}</td>
                  <td>{post.body}</td>
                </tr>
              );
            })}
        </tbody>
      </table>
    </div>
  );
}

What next

This is a brief overview of how I approach things. If you are interested in more detail you can checkout a full implementatin in this repo on GitHub.

My more advanced implementations include other features such as:

  • seperating out my database models and the GraphQL types and provided a type-safe mapping between the two
  • decoupling between the GraphQL layer and the data layer via the creation of services (for testability and scaling later)
  • typedi for dependancy injection of the services
  • request context creation including authentication and authorization

Hit me up on Twitter - @alexolivier if you have any questions!

Published Feb 10, 2021

cloud native product manager in london