Git Product home page Git Product logo

typed-graphqlify's Introduction

CircleCI npm version

image

typed-graphqlify

Build Typed GraphQL Query in TypeScript. Better TypeScript + GraphQL experience.

Install

npm install --save typed-graphqlify

Or if you use Yarn:

yarn add typed-graphqlify

Motivation

We all know that GraphQL is so great and solves many problems that we have with REST API, like overfetching and underfetching. But developing a GraphQL Client in TypeScript is sometimes a bit of pain. Why? Let's take a look at the example we usually have to make.

When we use GraphQL library such as Apollo, We have to define query and its interface like this:

interface GetUserQueryData {
  getUser: {
    id: number
    name: string
    bankAccount: {
      id: number
      branch?: string
    }
  }
}

const query = graphql(gql`
  query getUser {
    user {
      id
      name
      bankAccount {
        id
        branch
      }
    }
  }
`)

apolloClient.query<GetUserQueryData>(query).then(data => ...)

This is so painful.

The biggest problem is the redundancy in our codebase, which makes it difficult to keep things in sync. To add a new field to our entity, we have to care about both GraphQL and TypeScript interface. And type checking does not work if we do something wrong.

typed-graphqlify comes to address this issues, based on experience from over a dozen months of developing with GraphQL APIs in TypeScript. The main idea is to have only one source of truth by defining the schema using GraphQL-like object and a bit of helper class. Additional features including graphql-tag, or Fragment can be implemented by other tools like Apollo.

How to use

First, define GraphQL-like JS Object:

import { graphqlify, types } from 'typed-graphqlify'

const getUserQuery = {
  getUser: {
    user: {
      __params: { id: 1 },
      id: types.number,
      name: types.string,
      bankAccount: {
        id: types.number,
        branch: types.optional.string,
      },
    },
  },
}

Note that we use our types helper to define types in the result.

Then, convert the JS Object to GraphQL (string) with graphqlify:

const gqlString = graphqlify.query(getUserQuery)

console.log(gqlString)
// =>
//   query getUser {
//     user(id: 1) {
//       id
//       name
//       bankAccount {
//         id
//         branch
//       }
//     }
//   }

Finally, execute the GraphQL:

// GraphQLData is a type helper which returns one level down
import { GraphQLData } from 'typed-graphqlify'
import { executeGraphql } from 'some-graphql-request-library'

// We would like to type this!
const result: GraphQLData<typeof getUser> = await executeGraphql(gqlString)

// As we cast `result` to `typeof getUser`,
// Now, `result` type looks like this:
// interface result {
//   user: {
//     id: number
//     name: string
//     bankAccount: {
//       id: number
//       branch?: string
//     }
//   }
// }

image

Features

  • Nested Query
  • Array Query
  • Input variables, parameters
  • Query and Mutation
  • Optional types

Examples

Basic Query

query getUser {
  user {
    id
    name
    isActive
  }
}
graphqlify.query({
  getUser: {
    user: {
      id: types.number,
      name: types.string,
      isActive: types.boolean,
    },
  },
})

Basic Mutation

Change the first argument of graphqlify to mutation.

mutation updateUser($input: UserInput!) {
  updateUser(input: $input) {
    id
    name
  }
}
graphqlify.mutation({
  __params: { $input: 'UserInput!' },
  updateUser: {
    __params: { input: '$input' },
    id: types.number,
    name: types.string,
  },
})

Nested Query

Write nested object just like GraphQL.

query getUser {
  user {
    id
    name
    parent {
      id
      name
      grandParent {
        id
        name
        children {
          id
          name
        }
      }
    }
  }
}
graphqlify.query({
  getUser: {
    user: {
      id: types.number,
      name: types.string,
      parent: {
        id: types.number,
        name: types.string,
        grandParent: {
          id: types.number,
          name: types.string,
          children: {
            id: types.number,
            name: types.string,
          },
        },
      },
    },
  },
})

Array Field

Just add array to your query. This does not change the result of compile, but TypeScript can aware the field is array.

query getUsers {
  users(status: 'active') {
    id
    name
  }
}
graphqlify.query({
  getUsers: {
    users: [
      {
        __params: { status: 'active' },
        id: types.number,
        name: types.string,
      },
    ],
  },
})

Optional Field

Add types.optional or optional helper method to define optional field.

import { types, optional } from 'typed-graphqlify'

graphqlify.query({
  getUser: {
    user: {
      id: types.number,
      name: types.optional.string, // <-- user.name is `string | undefined`
      bankAccount: optional({      // <-- user.bankAccount is `{ id: number } | undefined`
        id: types.number,
      }),
    },
  },
}

Constant field

Use types.constant method to define constant field.

query getUser {
  user {
    id
    name
    __typename # <-- Always `User`
  }
}
graphqlify.query({
  getUser: {
    user: {
      id: types.number,
      name: types.string,
      __typename: types.constant('User'),
    },
  },
})

Enum field

Use types.oneOf method to define Enum field.

query getUser {
  user {
    id
    name
    type # <-- `Student` or `Teacher`
  }
}
enum UserType {
  'Student',
  'Teacher',
}

graphqlify.query({
  getUser: {
    user: {
      id: types.number,
      name: types.string,
      type: types.oneOf(UserType),
    },
  },
})

Note: Currently creating type from array element is not supported in TypeScript. See microsoft/TypeScript#28046

Multiple Queries

Add other queries at the same level of the other query.

query getFatherAndMother {
  father {
    id
    name
  }
  mother {
    id
    name
  }
}
graphqlify.query({
  getFatherAndMother: {
    father: {
      id: types.number,
      name: types.string,
    },
    mother: {
      id: types.number,
      name: types.number,
    },
  },
})

See more examples at src/index.test.ts

Why not use apollo client:codegen?

There are some GraphQL -> TypeScript convertion tools. The most famous one is Apollo codegen:

https://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output

In this section, we would like to explain why typed-graphqlify comes.

Disclaimer: I am not a heavy user of Apollo codegen, so the following points could be wrong. And I totally don't disrespect Apollo codegen.

Simplicity

Apollo codegen is a great tool. In addition to generating query interfaces, it does a lot of tasks including downloading schema, schema validation, fragment spreading, etc.

However, great usability is the tradeoff of complexity.

There are some issues to generate interfaces with Apollo codegen.

I (and maybe everyone) don't know the exact reasons, but Apollo's codebase is too large to find out what is the problem.

On the other hand, typed-graphqlify is as simple as possible tool by design, and the logic is quite easy. So I think if some issues happen we can fix them easily.

Multiple Queres problem

Currently Apollo codegen cannot handle multiple schemas.

Although I know this is a kind of edge case, but if we have the same type name on different schemas, which schema is taken?

typed-graphqlify works even without schema

Some graphql frameworks, such as laravel-graphql, cannot print schema as far as I know. I agree that we should avoid to use such frameworks, but there must be situations that we cannot get graphql schema for some reasons.

TODO

  • Optional support
  • Enum support

Thanks

Inspired by

typed-graphqlify's People

Contributors

acro5piano avatar capaj avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.