DEV Community

Kay Gosho
Kay Gosho

Posted on • Edited on

I've created a tiny library that creates GraphQL query from TypeScript, without losing type information

I created a tiny experimental library, typed-graphqlify.

https://github.com/acro5piano/typed-graphqlify

typed-graphqlify creates GraphQL query string from TypeScript's type definition-like file.
I would like to reduce pain to use TypeScript + GraphQL.

image.png

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 API 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 => ...) 
Enter fullscreen mode Exit fullscreen mode

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.

I know tools that convert GraphQL Schema to TypeScript like Apollo-CLI, graphql-code-generator, and graphqlgen.

However, it needs Schema update and optional query cannot be defined from actual query.
At least in my particular use case, typed-graphqlify is more useful.

How to use

Install it:

 yarn add typed-graphqlify 
Enter fullscreen mode Exit fullscreen mode

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, }, }, }, } 
Enter fullscreen mode Exit fullscreen mode

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 // } // } // } 
Enter fullscreen mode Exit fullscreen mode

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 // } // } // } 
Enter fullscreen mode Exit fullscreen mode

Features

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

Exapmles

Basic Query

 query getUser { user { id name } } 
Enter fullscreen mode Exit fullscreen mode
 graphqlify('query', { getUser: { user: { id: types.number, name: types.string, }, }, }) 
Enter fullscreen mode Exit fullscreen mode

Basic Mutation

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

For more examples and documentation, please take a look at github repo: https://github.com/acro5piano/typed-graphqlify

TODO

  • [x] Optional support
  • [ ] Enum support

Thanks

Inspired by

Top comments (0)