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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@untools/gql-client

v1.2.4

Published

A simple GraphQL client for making requests in JavaScript/TypeScript applications

Readme

@untools/gql-client

A modern, full-featured GraphQL client for JavaScript/TypeScript applications with support for queries, mutations, subscriptions, and file uploads.

Features

  • 🚀 Simple API - Easy to use with minimal setup
  • 📁 File Upload Support - Handle multipart form data for file uploads
  • 🔌 WebSocket Subscriptions - Real-time GraphQL subscriptions
  • 🔄 Auto-reconnection - Automatic WebSocket reconnection with exponential backoff
  • 🪵 Custom Logging - Pluggable logging system
  • 🌍 Environment Variables - Automatic configuration from environment
  • 📦 TypeScript Support - Full TypeScript support with type safety
  • 🎯 Lightweight - Minimal dependencies

Installation

npm install @untools/gql-client
# or
yarn add @untools/gql-client
# or
pnpm add @untools/gql-client

Quick Start

Basic Usage

import { executeGraphQL } from '@untools/gql-client';

const GET_USER_QUERY = `#graphql
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

const user = await executeGraphQL({
  query: GET_USER_QUERY,
  variables: { id: "123" },
  url: 'https://api.example.com/graphql',
  headers: {
    'Authorization': 'Bearer your-token'
  }
});

Using the GraphQL Client Class

import { GraphQLClient } from '@untools/gql-client';

const client = new GraphQLClient({
  apiUrl: 'https://api.example.com/graphql',
  apiKey: 'your-api-key', // Automatically adds x-api-key header
  wsUrl: 'wss://api.example.com/graphql', // For subscriptions
  headers: {
    'Custom-Header': 'value'
  }
});

// Execute queries/mutations
const result = await client.executeGraphQL()({
  query: GET_USER_QUERY,
  variables: { id: "123" }
});

File Upload Support

The client automatically handles file uploads using multipart form data:

const UPLOAD_FILE_MUTATION = `#graphql
  mutation UploadFile($file: Upload!) {
    uploadFile(file: $file) {
      id
      filename
      url
    }
  }
`;

// Single file upload
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = fileInput.files?.[0];

const result = await executeGraphQL({
  query: UPLOAD_FILE_MUTATION,
  variables: { file },
  url: 'https://api.example.com/graphql'
});

// Multiple files
const UPLOAD_MULTIPLE_MUTATION = `#graphql
  mutation UploadMultiple($files: [Upload!]!) {
    uploadMultiple(files: $files) {
      id
      filename
      url
    }
  }
`;

const files = Array.from(fileInput.files || []);
const result = await executeGraphQL({
  query: UPLOAD_MULTIPLE_MUTATION,
  variables: { files },
  url: 'https://api.example.com/graphql'
});

// Using the files option
const result = await executeGraphQL({
  query: UPLOAD_FILE_MUTATION,
  files: file, // or [file1, file2] or FileList
  url: 'https://api.example.com/graphql'
});

WebSocket Subscriptions

Basic Subscription

const client = new GraphQLClient({
  apiUrl: 'https://api.example.com/graphql',
  wsUrl: 'wss://api.example.com/graphql',
  apiKey: 'your-api-key'
});

const MESSAGES_SUBSCRIPTION = `#graphql
  subscription OnMessageAdded($chatId: ID!) {
    messageAdded(chatId: $chatId) {
      id
      content
      user {
        name
      }
      createdAt
    }
  }
`;

const unsubscribe = await client.subscribe({
  query: MESSAGES_SUBSCRIPTION,
  variables: { chatId: "chat-123" },
  onNext: (data) => {
    console.log('New message:', data.messageAdded);
  },
  onError: (error) => {
    console.error('Subscription error:', error);
  },
  onComplete: () => {
    console.log('Subscription completed');
  }
});

// Unsubscribe when done
unsubscribe();

WebSocket Management

// Check connection status
const isConnected = client.isWebSocketConnected();

// Manually reconnect
client.reconnectWebSocket();

// Update authentication token
client.updateWebSocketAuth('new-token');

// Close WebSocket connection
client.closeWebSocket();

Using Standalone WebSocket Client

import { GraphQLWebSocketClient } from '@untools/gql-client';

const wsClient = new GraphQLWebSocketClient(
  'wss://api.example.com/graphql',
  { authorization: 'Bearer your-token' }, // Connection params
  ['graphql-ws'], // Protocols
  console // Logger
);

const unsubscribe = await wsClient.subscribe({
  query: MESSAGES_SUBSCRIPTION,
  variables: { chatId: "chat-123" },
  onNext: (data) => console.log(data),
  onError: (error) => console.error(error)
});

Environment Variables

The client automatically reads from environment variables:

NEXT_PUBLIC_GRAPHQL_API_URL=https://api.example.com/graphql
NEXT_PUBLIC_GRAPHQL_WS_URL=wss://api.example.com/graphql
API_KEY=your-api-key

Then use the default instances:

import { executeGraphQL, defaultGraphQLClient } from '@untools/gql-client';

// Uses environment variables automatically
const result = await executeGraphQL({
  query: GET_USER_QUERY,
  variables: { id: "123" }
});

// Or use the default client
const subscription = await defaultGraphQLClient.subscribe({
  query: MESSAGES_SUBSCRIPTION,
  variables: { chatId: "chat-123" },
  onNext: (data) => console.log(data)
});

Custom Logging

const client = new GraphQLClient({
  apiUrl: 'https://api.example.com/graphql',
  apiKey: 'your-api-key',
  logger: {
    log: (...args) => console.log('[GraphQL]', ...args),
    error: (message, data) => console.error(`[GraphQL Error] ${message}`, data)
  }
});

Advanced Usage

Error Handling

try {
  const result = await executeGraphQL({
    query: GET_USER_QUERY,
    variables: { id: "123" },
    url: 'https://api.example.com/graphql'
  });
} catch (error) {
  if (error.message.includes('User not found')) {
    // Handle specific GraphQL error
  } else {
    // Handle network or other errors
  }
}

Custom Headers per Request

const client = new GraphQLClient({
  apiUrl: 'https://api.example.com/graphql',
  headers: {
    'Default-Header': 'value'
  }
});

const result = await client.executeGraphQL()({
  query: GET_USER_QUERY,
  variables: { id: "123" },
  headers: {
    'Request-Specific-Header': 'value',
    'Authorization': 'Bearer different-token' // Override default
  }
});

Direct GraphQL Request

For more control, use the low-level graphqlRequest function:

import { graphqlRequest } from '@untools/gql-client';

const response = await graphqlRequest({
  url: 'https://api.example.com/graphql',
  options: {
    query: GET_USER_QUERY,
    variables: { id: "123" },
    files: fileList // Optional file uploads
  },
  headers: {
    'Authorization': 'Bearer token'
  },
  apiKey: 'your-api-key'
});

// Handle response.data and response.errors manually
if (response.errors) {
  console.error('GraphQL errors:', response.errors);
} else {
  console.log('Data:', response.data);
}

API Reference

GraphQLClient

Constructor Options

interface GraphQLClientConfig {
  apiUrl?: string;           // Default GraphQL endpoint
  apiKey?: string;           // Default API key (adds x-api-key header)
  headers?: Record<string, string>;  // Default headers
  logger?: Logger;           // Custom logger
  wsUrl?: string;            // WebSocket URL for subscriptions
  wsHeaders?: Record<string, string>; // WebSocket-specific headers
  wsProtocols?: string | string[];    // WebSocket protocols
}

Methods

  • executeGraphQL() - Returns a function to execute GraphQL operations
  • subscribe(options) - Create a GraphQL subscription
  • graphqlRequest(params) - Low-level GraphQL request
  • closeWebSocket() - Close WebSocket connection
  • reconnectWebSocket() - Reconnect WebSocket
  • isWebSocketConnected() - Check WebSocket connection status
  • updateWebSocketAuth(token) - Update WebSocket authentication

Functions

executeGraphQL

executeGraphQL<TResponse, TVariables>({
  query: string;
  variables?: TVariables;
  headers?: Record<string, string>;
  url?: string;
  files?: File[] | FileList | File;
}): Promise<TResponse>

graphqlRequest

graphqlRequest<T>({
  url: string;
  options: {
    query: string;
    variables?: Record<string, unknown>;
    files?: File[] | FileList | File;
  };
  headers?: Record<string, string>;
  apiKey?: string;
}): Promise<GraphQLResponse<T>>

Types

interface GraphQLResponse<T> {
  data?: T;
  errors?: Array<{
    message: string;
    locations?: { line: number; column: number }[];
    path?: string[];
    extensions?: Record<string, any>;
  }>;
}

interface SubscriptionOptions {
  query: string;
  variables?: Record<string, unknown>;
  onNext?: (data: any) => void;
  onError?: (error: Error) => void;
  onComplete?: () => void;
}

interface Logger {
  log: (...args: any[]) => void;
  error: (message: string, data?: any) => void;
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.