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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@travelpass/graphql-client

v0.0.3

Published

A minimal, typed GraphQL client. A focused port of graphql-request: typed documents in, typed data out, with middleware hooks for everything else.

Readme

@travelpass/graphql-client

A small, typed GraphQL client for the main case: POST a typed document, get typed data back.

It started as a trimmed-down fork of graphql-request. It keeps what most apps actually use and drops the rest.

  • Typed results and variables from your codegen documents
  • Required variables are enforced at compile time
  • Per-request headers, merged over client headers
  • Errors carry partial data, status and headers
  • Three extension points: requestMiddleware, responseMiddleware, and a custom fetch

Install

npm install @travelpass/graphql-client graphql

graphql is a peer dependency.

Quick start

import { createGraphQLClient } from '@travelpass/graphql-client'
import { gql } from 'src/__generated__/gql'

const client = createGraphQLClient('https://api.example.com/graphql', {
  headers: {
    authorization: `Bearer ${token}`,
  },
})

const getHotelQuery = gql(`
  query GetHotel($hotelId: ID!) {
    hotel(id: $hotelId) { 
      id
      name
    }
  }
`)

const data = await client.request({
  document: getHotelQuery,
  variables: {
    hotelId,
  },
})
// typed
data.hotel.name

Variables

Variables are typed from the document, and the compiler decides whether they are required.

// no variables: `variables` optional
await client.request({
  document: currentUserQuery,
})

// has variables: required
await client.request({
  document: getHotelQuery,
  variables: {
    hotelId,
  },
})

// compile error: missing variables
await client.request({
  document: getHotelQuery,
})

Request headers

Headers set on the client are sent with every request. Pass headers to add or override headers for one call:

await client.request({
  document: EMIT_COLOSSUS_EVENT,
  headers: {
    'x-colossus-metadata': colossusMetadata,
  },
  variables: {
    input,
  },
})

Headers are plain Record<string, string> objects. Header names are lowercased before merging, so a per-request header replaces the client header of the same name regardless of casing.

Reading response headers

request returns data. When a call needs something from the response headers, pass onResponseHeaders:

let searchRequestId: string | null = null

const data = await client.request({
  document: SEARCH_HOTELS,
  onResponseHeaders: headers => {
    searchRequestId = headers.get('x-request-id')
  },
  variables,
})

It runs only on successful responses. On failure, headers are on the error (see below).

Errors

A failed request throws a ClientError when:

  • the response contains GraphQL errors (even with HTTP 200), or
  • the HTTP status is not 2xx.
import { ClientError } from '@travelpass/graphql-client'

try {
  await client.request({
    document: VALIDATE_RATE,
    variables: {
      rateToken,
    },
  })
} catch (error) {
  if (error instanceof ClientError) {
    error.response.data // partial data, if the server returned any
    error.response.errors // GraphQL errors
    error.response.status // HTTP status
    error.response.headers // response headers
    error.response.body // raw body text, useful for non-GraphQL 4xx/5xx
  }
}

Network failures and aborted requests (including timeouts) are not ClientErrors. They throw whatever fetch throws, such as a TimeoutError DOMException.

Configuration

createGraphQLClient(url, {
  headers, // Record<string, string>, or a function returning one (called per request)
  fetch, // custom fetch; defaults to globalThis.fetch
  requestMiddleware, // change the request before it is sent
  responseMiddleware, // observe every completed response
  ...requestInit, // any other RequestInit option (credentials, cache, ...)
})

requestMiddleware

Runs before every request. Receives the request (including operationName and variables) and returns the request to send. Use it to change headers, the URL, or the signal.

Per-operation timeouts:

const timeouts: Record<string, number> = {
  ResultsSearchHotels: 15_000,
}

const client = createGraphQLClient(url, {
  headers,
  requestMiddleware: req => ({
    ...req,
    signal: AbortSignal.timeout(timeouts[req.operationName ?? ''] ?? 5_000),
  }),
})

responseMiddleware

Runs after every completed response, success or error. It observes and cannot change the result. Use it for logging and metrics.

const client = createGraphQLClient(url, {
  headers,
  responseMiddleware: (response, req) => {
    const status = response instanceof Error ? 'error' : 'success'
    sendStatsdMetric('graphql_request', {
      tags: [`operation:${req.operationName}`, `status:${status}`],
    })
  },
})

It does not run when the request never completes (network failure, abort, timeout), because fetch throws before there is a response. Catch those at the call site, or wrap request.

Custom fetch

Swap the transport for retries, tracing, caching, or anything else that works at the fetch level:

const client = createGraphQLClient(url, {
  fetch: myInstrumentedFetch,
})

Types

import type { GraphQLClient } from '@travelpass/graphql-client'

const withTracking = (client: GraphQLClient): GraphQLClient => ({
  request: async requestArgs => {
    // ...track, then:
    return client.request(requestArgs)
  },
})

Because the client is a plain object of functions, request can be destructured or passed around without losing this.

Scope

This client covers the main case on purpose. It does not support:

  • GET requests
  • Batching
  • File uploads
  • Subscriptions
  • errorPolicy (partial data is available on ClientError.response.data)
  • Custom JSON serializers

Most of these can be built on the extension points above if you need them.

Migrating from graphql-request

| graphql-request | This client | | ------------------------------------------------------------------ | ------------------------------------------------------------ | | new GraphQLClient(url, config) | createGraphQLClient(url, config) | | client.request(doc, variables) | client.request({ document, variables }) | | client.rawRequest(print(doc), variables) then response.headers | client.request({ document, variables, onResponseHeaders }) | | client.request(doc, variables, requestHeaders) | client.request({ document, variables, headers }) | | import { ClientError } from '@travelpass/graphql-request' | import { ClientError } from '@travelpass/graphql-client' |

Update every ClientError import. instanceof only matches errors from the same package.

License

MIT

Contributing

Contributions are welcome. See CONTRIBUTING.md for setup, scripts, and how to open a pull request.