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.
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 => ...)
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
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:
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
// }
// }
// }
Features
- Nested Query
- Input variables, parameters
- Query and Mutation
- Optional types
Exapmles
Basic Query
query getUser {
user {
id
name
}
}
graphqlify('query', {
getUser: {
user: {
id: types.number,
name: types.string,
},
},
})
Basic 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,
},
})
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)