npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphtype

v0.0.10

Published

GraphQL query builder for typescript. Write GQL queries as TS objects and let compiler typecheck your queries

Downloads

50

Readme

npm version Build Status

graphtype

Build Type-safe GraphQL Queries in TypeScript. Let the compiler typecheck your queries.

image

Install

npm install graphtype

Or if you use Yarn:

yarn add graphtype

Motivation

GraphQL is great and solves many problems, like overfetching and underfetching. Although writing queries 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 usually 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 duplicates the shape of the object. To add a new field to our entity, we have to edit both GraphQL query and TypeScript interface. And TS checking does not work if we do something wrong.

graphtype tries to solve this. 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.

How to use

First, define GraphQL-like JS Object:

import { graphqlify, t, params } from 'graphtype'

const getUserQuery = {
  user: params(
    { id: 1 },
    {
      id: t.Number,
      name: t.String,
      bankAccount: {
        id: t.Number,
        branch: t.nullable.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 {
//     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 'graphtype'
import { executeGraphql } from 'some-graphql-request-library'

// We would like to type this!
const result: GraphQLData<typeof getUserQuery> = 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
//     }
//   }
// }

Features

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

Examples

Basic Query

query getUser {
  user {
    id
    name
    isActive
  }
}
graphqlify.query(
  {
    user: {
      id: types.Number,
      name: types.String,
      isActive: types.boolean
    }
  },
  'getUser'
)

Basic Mutation

Change the first argument of graphqlify to mutation.

mutation updateUser($input: UserInput!) {
  updateUser(input: $input) {
    id
    name
  }
}
graphqlify.mutation(
  {
    updateUser: params(
      { input: types.raw('UserInput') },
      {
        id: types.Number
      }
    )
  },
  'updateUser'
)

Mutation with a named variable

By default graphql variables are named the same as they are defined in the field. When you have multiple fields with the same variable name, you need to rename them. Use $ for this:

import { graphqlify, types, params, $ } from 'graphtype'
graphqlify.mutation(
  {
    updateUser: params(
      { input: $('userInput', types.raw('UserInput')) },
      {
        id: types.Number
      }
    )
  },
  'updateUser'
)

this results as:

mutation updateUser($userInput: UserInput!) {
  updateUser(input: $userInput) {
    id
    name
  }
}

Nested Query

Write nested object just like GraphQL.

query getUser {
  user {
    id
    name
    parent {
      id
      name
      grandParent {
        id
        name
        children {
          id
          name
        }
      }
    }
  }
}
graphqlify.query(
  {
    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
          }
        }
      }
    }
  },
  'getUser'
)

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: params({ status: types.String }, [
      {
        id: types.Number
      }
    ])
  },
  'getUsers'
)

Nullable Fields

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

import { types, nullable } from 'graphtype'

graphqlify.query(
  {
    user: {
      id: types.Number,
      name: types.nullable.String, // <-- user.name is `String | undefined`
      bankAccount: nullable({
        // <-- user.bankAccount is `{ id: Number } | undefined`
        id: types.Number
      })
    }
  },
  'getUser'
)

Constant field

Use types.constant method to define constant field.

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

Enum field

Use types.enum method to define Enum field.

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

graphqlify.query(
  {
    user: {
      id: types.Number,
      name: types.String,
      type: types.enum(UserType)
    }
  },
  'getUser'
)

Note: Currently creating type from array element is not supported in TypeScript. See https://github.com/Microsoft/TypeScript/issues/28046

Multiple Queries

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

query getFatherAndMother {
  father {
    id
    name
  }
  mother {
    id
    name
  }
}
graphqlify.query(
  {
    father: {
      id: types.Number,
      name: types.String
    },
    mother: {
      id: types.Number,
      name: types.Number
    }
  },
  'getFatherAndMother'
)

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 graphtype 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.

  • https://github.com/apollographql/apollo-tooling/issues/791
  • https://github.com/apollographql/apollo-tooling/issues/678

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, graphtype 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 Schema problem

Currently Apollo codegen cannot handle multiple schemas.

  • https://github.com/apollographql/apollo-tooling/issues/588
  • https://github.com/apollographql/apollo-tooling/issues/554

Related projects

This project was initially forked from an early version of

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

Currently it is much more like this project: https://github.com/helios1138/graphql-typed-client

  • Probably most important difference is graphql-typed-client requires whole schema.json to be loaded at runtime. This generated file can be very big for complex schemas, so that's why I decided to maintain project as a ligtweight alternative to graphql-typed-client.