awesome-graphql-client
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published


CI/CD npm version Codecov

Awesome GraphQL Client

GraphQL Client with file upload support for NodeJS and browser

Features

  • GraphQL File Upload support
  • Works in browsers and NodeJS
  • Zero dependencies
  • Small size (around 2Kb gzipped)
  • Full Typescript support
  • Supports queries generated by graphql-tag
  • Supports GraphQL GET requests
  • Perfect for React apps in combination with react-query. See Next.js example

Install

npm install awesome-graphql-client

Quick Start

Browser

import { AwesomeGraphQLClient } from 'awesome-graphql-client'

const client = new AwesomeGraphQLClient({ endpoint: '/graphql' })

// Also query can be an output from graphql-tag (see examples below)
const GetUsers = `
  query getUsers {
    users {
      id
    }
  }
`

const UploadUserAvatar = `
  mutation uploadUserAvatar($userId: Int!, $file: Upload!) {
    updateUser(id: $userId, input: { avatar: $file }) {
      id
    }
  }
`

client
  .request(GetUsers)
  .then(data =>
    client.request(UploadUserAvatar, {
      id: data.users[0].id,
      file: document.querySelector('input#avatar').files[0],
    }),
  )
  .then(data => console.log(data.updateUser.id))
  .catch(error => console.log(error))

NodeJS

const { AwesomeGraphQLClient } = require('awesome-graphql-client')
const FormData = require('form-data')
const { createReadStream } = require('fs')
const http = require('http')
const fetch = require('node-fetch')

const client = new AwesomeGraphQLClient({
  endpoint: 'http://localhost:8080/graphql',
  fetch,
  FormData, // Required only if you're using file upload
  fetchOptions: {
    // Using HTTP Keep-Alive will make requests ~2x faster in NodeJS:
    // https://github.com/Ethan-Arrowood/undici-fetch/blob/main/benchmarks.md#fetch
    agent: new http.Agent({ keepAlive: true }),
  },
})

// Also query can be an output from graphql-tag (see examples below)
const UploadUserAvatar = `
  mutation uploadUserAvatar($userId: Int!, $file: Upload!) {
    updateUser(id: $userId, input: { avatar: $file }) {
      id
    }
  }
`

client
  .request(UploadUserAvatar, { file: createReadStream('./avatar.img'), userId: 10 })
  .then(data => console.log(data.updateUser.id))
  .catch(error => console.log(error))

For even better performance check out undici example

Table of Contents

API

AwesomeGraphQLClient

Usage:

import { AwesomeGraphQLClient } from 'awesome-graphql-client'
const client = new AwesomeGraphQLClient(config)

config properties

  • endpoint: string - The URL to your GraphQL endpoint (required)
  • fetch: Function - Fetch polyfill (necessary in NodeJS, see example)
  • fetchOptions: object - Overrides for fetch options
  • FormData: object - FormData polyfill (necessary in NodeJS if you are using file upload, see example)
  • formatQuery: function(query: any): string - Custom query formatter (see example)
  • onError: function(error: GraphQLRequestError | Error): void - Provided callback will be called before throwing an error (see example)
  • isFileUpload: function(value: unknown): boolean - Custom predicate function for checking if value is a file (see example)

client methods

  • client.setFetchOptions(fetchOptions: FetchOptions): Sets fetch options. See examples below
  • client.getFetchOptions(): Returns current fetch options
  • client.setEndpoint(): string: Sets a new GraphQL endpoint
  • client.getEndpoint(): string: Returns current GraphQL endpoint
  • client.request(query, variables?, fetchOptions?): Promise<data>: Sends GraphQL Request and returns data or throws an error
  • client.requestSafe(query, variables?, fetchOptions?): Promise<{ data, response } | { error }>: Sends GraphQL Request and returns object with 'ok: true', 'data' and 'response' or with 'ok: false' and 'error' fields. See examples below. Notice: this function never throws.

GraphQLRequestError

instance fields

  • message: string - Error message
  • query: string - GraphQL query
  • variables: string | undefined - GraphQL variables
  • response: Response - response returned from fetch

Examples

Typescript

interface getUser {
  user: { id: number; login: string } | null
}
interface getUserVariables {
  id: number
}

const query = `
  query getUser($id: Int!) {
    user {
      id
      login
    }
  }
`

const client = new AwesomeGraphQLClient({
  endpoint: 'http://localhost:3000/graphql',
})

client
  .request<getUser, getUserVariables>(query, { id: 10 })
  .then(data => console.log(data))
  .catch(error => console.log(error))

client.requestSafe<getUser, getUserVariables>(query, { id: 10 }).then(result => {
  if (!result.ok) {
    throw result.error
  }
  console.log(`Status ${result.response.status}`, `Data ${result.data.user}`)
})

Typescript with TypedDocumentNode (even better!)

You can generate types from queries by using GraphQL Code Generator with TypedDocumentNode plugin

# queries.graphql
query getUser($id: Int!) {
  user {
    id
    login
  }
}
// index.ts
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { AwesomeGraphQLClient } from 'awesome-graphql-client'
import { print } from 'graphql/language/printer'

import { GetCharactersDocument } from './generated'

const gqlClient = new AwesomeGraphQLClient({
  endpoint: 'https://rickandmortyapi.com/graphql',
  formatQuery: (query: TypedDocumentNode) => print(query),
})

// AwesomeGraphQLClient will infer all types from the passed query automagically:
gqlClient
  .request(GetCharactersDocument, { name: 'Rick' })
  .then(data => console.log(data))
  .catch(error => console.log(error))

Check out full example at examples/typed-document-node

Error Logging

import { AwesomeGraphQLClient, GraphQLRequestError } from 'awesome-graphql-client'

const client = new AwesomeGraphQLClient({
  endpoint: '/graphql',
  onError(error) {
    if (error instanceof GraphQLRequestError) {
      console.error(error.message)
      console.groupCollapsed('Operation:')
      console.log({ query: error.query, variables: error.variables })
      console.groupEnd()
    } else {
      console.error(error)
    }
  },
})

GraphQL GET Requests

Internally it uses URLSearchParams API. Consider polyfilling URL standard for this feature to work in IE

client
  .request(query, variables, { method: 'GET' })
  .then(data => console.log(data))
  .catch(err => console.log(err))

GraphQL Tag

Approach #1: Use formatQuery

import { AwesomeGraphQLClient } from 'awesome-graphql-client'
import { DocumentNode } from 'graphql/language/ast'
import { print } from 'graphql/language/printer'
import gql from 'graphql-tag'

const client = new AwesomeGraphQLClient({
  endpoint: '/graphql',
  formatQuery: (query: DocumentNode | string) =>
    typeof query === 'string' ? query : print(query),
})

const query = gql`
  query me {
    me {
      login
    }
  }
`

client
  .request(query)
  .then(data => console.log(data))
  .catch(err => console.log(err))

Approach #2: Use fake graphql-tag

Recommended approach if you're using graphql-tag only for syntax highlighting and static analysis such as linting and types generation. It has less computational cost and makes overall smaller bundles. GraphQL fragments are supported too.

import { AwesomeGraphQLClient, gql } from 'awesome-graphql-client'

const client = new AwesomeGraphQLClient({ endpoint: '/graphql' })

const query = gql`
  query me {
    me {
      login
    }
  }
`

client
  .request(query)
  .then(data => console.log(data))
  .catch(err => console.log(err))

Approach #3: Use TypedDocumentNode instead

Perfect for Typescript projects. See example above

Cookies in NodeJS

const { AwesomeGraphQLClient } = require('awesome-graphql-client')
const nodeFetch = require('node-fetch')

const client = new AwesomeGraphQLClient({
  endpoint: 'http://localhost:8080/graphql',
  fetch: require('fetch-cookie')(nodeFetch),
})

Custom isFileUpload Predicate

const { AwesomeGraphQLClient, isFileUpload } = require('awesome-graphql-client')

const client = new AwesomeGraphQLClient({
  endpoint: 'http://localhost:8080/graphql',
  // By default File, Blob, Buffer, Promise and stream-like instances are considered as files.
  // You can expand this behaviour by adding a custom predicate
  isFileUpload: value => isFileUpload(value) || value instanceof MyCustomFile,
})

More Examples

https://github.com/lynxtaa/awesome-graphql-client/tree/master/examples

Package Sidebar

Install

npm i awesome-graphql-client

Weekly Downloads

3,865

Version

1.0.1

License

MIT

Unpacked Size

62.9 kB

Total Files

19

Last publish

Collaborators

  • lynxtaa