Giter Club home page Giter Club logo

awesome-graphql-client's Introduction

Logo

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

NodeJS 20

const { openAsBlob } = require('node:fs')
const { AwesomeGraphQLClient } = require('awesome-graphql-client')

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

// 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
    }
  }
`

const blob = await openAsBlob('./avatar.png')

client
  .request(UploadUserAvatar, { file: new File([blob], 'avatar.png'), userId: 10 })
  .then(data => console.log(data.updateUser.id))
  .catch(error => console.log(error))

NodeJS 18

const { createReadStream, statSync } = require('node:fs')
const path = require('node:path')
const { Readable } = require('node:stream')
const { AwesomeGraphQLClient } = require('awesome-graphql-client')

class StreamableFile extends Blob {
  constructor(filePath) {
    const { mtime, size } = statSync(filePath)

    super([])

    this.name = path.parse(filePath).base
    this.lastModified = mtime.getTime()
    this.#filePath = filePath

    Object.defineProperty(this, 'size', {
      value: size,
      writable: false,
    })
    Object.defineProperty(this, Symbol.toStringTag, {
      value: 'File',
      writable: false,
    })
  }

  stream() {
    return Readable.toWeb(createReadStream(this.#filePath))
  }
}

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

// 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: new StreamableFile('./avatar.png'), userId: 10 })
  .then(data => console.log(data.updateUser.id))
  .catch(error => console.log(error))

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
  • fetchOptions: object - Overrides for fetch options
  • FormData: object - FormData polyfill
  • 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 fetchCookie = require('fetch-cookie')

const client = new AwesomeGraphQLClient({
  endpoint: 'http://localhost:8080/graphql',
  fetch: fetchCookie(globalThis.fetch),
})

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

awesome-graphql-client's People

Contributors

codebdy avatar dependabot[bot] avatar k9ordon avatar lynxtaa avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

awesome-graphql-client's Issues

Next.js example

Hello, would be cool to have a Next.js example in README showing how to combine clientside and server side fetching

add the ability to change fetchOptions (or only headers) per request

Is your feature request related to a problem? Please describe.
Would be nice if you can setFetchOptions() (or only headers) per request.

Describe the solution you'd like
Setting some default headers in initial setup

const graphQlClient = new AwesomeGraphQLClient({
    endpoint: GRAPHQL_ENDPOINT,
    fetchOptions: {
          headers: {
            ...some default headers
        }
    }
});

// request with default headers/fetchOptions
graphQlClient.request(document, variables)

if some requests need an additional header

graphQlClient
    .setFetchOptions({
          headers: {
            ...additional headers
        }
    })
    .request(document, variables)

or only headers

graphQlClient
    .setHeaders({
        ...additional headers
    })
    .request(document, variables)

Describe alternatives you've considered
N/A

Additional context
N/A

Subscriptions support

Is your feature request related to a problem? Please describe.
Is it planned to add subscriptions support?

Describe the solution you'd like
Would like to use graphql subscriptions

Describe alternatives you've considered
N/A

Additional context
N/A

Sending query by `GET` fails when endpoint starts with `/`

Describe the bug
Sending query by GET fails when endpoint starts with / .

To Reproduce

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

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

await client.request(SomeQuery, variables, {method: "GET"})

This code shows an error: TypeError: Failed to construct 'URL': Invalid URL

Expected behavior
The client should accept a relative path as an endpoint.

Additional context
It looks caused by this function:

export function formatGetRequestUrl({
endpoint,
query,
variables,
}: {
endpoint: string
query: string
variables?: Record<string, unknown>
}): string {
const url = new URL(endpoint)

URL constructor requires an absolute url or a relative path with a base url. See https://developer.mozilla.org/en-US/docs/Web/API/URL/URL

Support sending `operationName`

OperationName is useful for logging purposes, frameworks like HotChocolate have a built-in field to handle it

image

Describe the solution you'd like
Pass operationName in the request payload

Describe alternatives you've considered
using a customFetch operation and extracting the operationName from the queries sent in

Additional context
Add any other context or screenshots about the feature request here.

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Header Customization

Is your feature request related to a problem? Please describe.
It is common to have some authenticated urls which requires a token in header to give you access to the desired resource.
Unfortunately I didn't see any way to set my preferred header variable for authorization.

Describe the solution you'd like
Provide a method to give user ability to set necessary headers. For example:

client.setHeader({
     variable: value
})

add option to change endpoint

Is your feature request related to a problem? Please describe.
Would be helpful if we can set the endpoint like the fetchOptions.

Describe the solution you'd like
add the possibility to set the endpoint like this client.setEndpoint(string).

Describe alternatives you've considered
NA

Additional context
NA

Support for a custom appendFile function to be configured

Is your feature request related to a problem? Please describe.
I would like to have the ability to customise how the objects I determine to be files are appended to the FormData instance.

Describe the solution you'd like
When the AwesomeGraphQLClient instance is created, pass an optional custom appendFile function that can be used as an escape hatch to append the file to the form

Describe alternatives you've considered
I would like this so I can set the file name on the form. Uncertain of how to do this otherwise.

Additional context
Libraries such as https://www.npmjs.com/package/apollo-upload-client allow for such an option.

Support for persisted queries?

Is your feature request related to a problem? Please describe.
It would be great if this library support persisted queries, which means the server already knows the query the client is sending, and the client merely passes an "id" in the query string (GET) or "id" in the body (POST) rather than a query in the body

Describe the solution you'd like
A way for graphClient.request to pass an id corresponding to a query, or an alternative graphClient.requestId() where the first arg is an ID rather than the query itself

Describe alternatives you've considered
Alternatively, the library could provide a way to customize how operations are serialized, instead of just a hard-coded JSON.stringify(...)

Typescript typings for package

Is your feature request related to a problem? Please describe.
When using this package in a typescript project I receive following error:

/path-to-project/node_modules/awesome-graphql-client/dist/index.modern.mjs' implicitly has an 'any' type.
  There are types at '/path-to-project/node_modules/awesome-graphql-client/dist/index.d.ts', but this result could not be resolved when respecting package.json "exports". The 'awesome-graphql-client' library may need to update its package.json or typings.

NOTE: This problem is produced by vscode.

Describe the solution you'd like
I prefer to have a complete typing when using project or there should be a package like @types/awesome-graphql-client to add necessary typings.

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.