Giter Club home page Giter Club 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 = {
  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('getUser', getUserQuery)

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

Finally, execute the GraphQL:

import { executeGraphql } from 'some-graphql-request-library'

// We would like to type this!
const result: 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,
  },
})

Or without query name

query {
  user {
    id
    name
    isActive
  }
}
graphqlify.query({
  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('users', {
  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,
  },
})

Query Alias

Query Alias is implemented in dynamic property.

query getMaleUser {
  maleUser: user {
    id
    name
  }
}
graphqlify.query('getMaleUser', {
  [alias('maleUser', 'user')]: {
    id: types.number,
    name: types.string,
  },
}

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.

Write GraphQL programmatically

It is useful to write GraphQL programmatically, although that is an edge case.

Imagine AWS management console:

image

If you build something like that with GraphQL, you have to build GraphQL dynamically and programmatically.

typed-graphqlify works for such cases without losing type information.

TODO

  • Optional support
  • Enum support

Contributing

To get started with a development installation of the typed-graphqlify, follow the instructions at our Contribution Guide.

Thanks

Inspired by

typed-graphqlify's People

Contributors

acro5piano avatar yardwill avatar capaj avatar arjunyel avatar

Watchers

James Cloos avatar Mr. Guilherme Gomes 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.