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

@yonus_amire01/gqlx

v1.0.0

Published

Prisma-style, codegen-driven GraphQL client. No .gql files — your call site is the query.

Readme

gqlx

Prisma-style, codegen-driven GraphQL client for TypeScript.

No .gql files. No template strings. The call site itself is the query.

npm version npm downloads bundle size license types


const data = await gql.query({
  findUserById: {
    id: 'u_1',
    include: { id: true, email: true, posts: { include: { title: true } } },
  },
});

// data.findUserById.data is exactly { id: string; email: string; posts: { title: string }[] }
// Nothing more. Nothing less. No casts. No `any`.

Why gqlx

GraphQL clients usually force a tradeoff: either you write .gql strings (and lose colocation, refactor safety, and IDE flow), or you adopt a generator that produces hooks tied to specific queries. gqlx removes the tradeoff. The selection object is the query, the response type is narrowed to exactly the fields you picked, and one round-trip can target multiple root fields — all driven by an introspected, fully typed descriptor of your schema.

  • Zero .gql files — write selections inline as TypeScript objects.
  • Precise return typesSelected<T, S> narrows the response to the shape you asked for, with no casts.
  • Argument inferencewhere, data, filter, etc. are typed from the schema, every value lifted to a $variable.
  • Multi-endpoint — merge several GraphQL services into one client. One config, one generator, one import.
  • Pluggable transport — axios (default), fetch, or BYO function to bridge into an existing SDK.
  • Unions & interfaces__on carries inline fragments with per-type selections, all typed.
  • Single round-trip batching — pass an array of selections; gqlx merges them into one operation.

Install

npm install gqlx
# or
pnpm add gqlx
# or
yarn add gqlx

Requires Node >=18.17. TypeScript >=5.0 recommended.

Quickstart

1. Scaffold a config.

npx gqlx init

This drops a gql.config.ts at the project root. Open it and point schemas at your endpoint(s).

2. Generate the typed client.

npx gqlx generate

This introspects every endpoint and writes the typed descriptors to node_modules/.gqlx/. Re-run whenever the schema changes — renamed or removed fields become TypeScript errors at every call site.

3. Create the client (one file in your app).

// src/gql.ts
import { createUseGql } from 'gqlx/generated';

export const gql = createUseGql({
  endpoint: 'http://localhost:3010/graphql',
});

4. Query.

import { gql } from './gql';

const data = await gql.query({
  findUsers: {
    where: { role: 'ADMIN' },
    include: { id: true, email: true },
  },
});

await gql.mutate({
  createUser: {
    data: { email: '[email protected]' },
    include: { id: true },
  },
});

That's it. No .gql files. No hooks generated per query. No unknown returns.

Table of contents

Configuration

gql.config.ts lives at the project root. defineConfig is an identity helper that gives you IntelliSense.

import { defineConfig } from 'gqlx';

export default defineConfig({
  // One or more endpoints. Multiple schemas merge into a single client.
  // Root-field collisions throw at codegen time — disambiguate with __field.
  schemas: [
    'http://localhost:3010/graphql',
    { url: 'http://localhost:6050/graphql', headers: { 'x-internal': '1' } },
  ],

  // Where to write generated files.
  // Default: node_modules/.gqlx (works with `import { createUseGql } from 'gqlx/generated'`)
  // Set a path under src/ if you'd rather commit the output.
  // output: './src/generated/gqlx',

  // Scalar → TS overrides. Merged onto defaults (String, Int, ID, Float, Boolean).
  // scalars: { BigInt: 'string', Decimal: 'string', DateTime: 'string' },

  // Fields whose value is JSON (no sub-selection). `*` matches any parent.
  // Default: ['*.props', '*.metadata']
  // freeSchemaFields: ['*.props', '*.metadata', 'Event.payload'],

  // npm package the generated client imports `createClient` from.
  // Default: 'gqlx'. Override if you've wrapped this library.
  // runtimePackage: '@acme/gqlx',
});

| Field | Type | Default | Description | | ------------------ | -------------------------- | ----------------------------- | -------------------------------------------------------------------- | | schemas | (string \| { url, headers? })[] | required | Endpoints to introspect. | | output | string | node_modules/.gqlx | Directory to write generated files. | | scalars | Record<string, string> | built-in | Scalar-name → TS-type overrides. | | freeSchemaFields | string[] | ['*.props', '*.metadata'] | Field paths typed as JsonObject (selected as true, no sub-fields). | | runtimePackage | string | 'gqlx' | Package the generated client imports createClient from. |

Transports

createUseGql accepts one of three shapes — pick whichever fits your environment.

1. axios (default)

createUseGql({
  endpoint: 'https://api.example.com/graphql',
  headers: { authorization: () => `Bearer ${getToken()}` },
});

To inherit interceptors from an axios instance you already own:

import { http } from './http'; // your configured AxiosInstance

createUseGql({ endpoint: '/graphql', axios: http });

2. fetch

For axios-free environments (edge runtimes, workers, the browser fetch).

createUseGql({
  endpoint: '/graphql',
  adapter: 'fetch',
  headers: () => ({ authorization: `Bearer ${getToken()}` }),
});

3. Bring your own transport

For an existing SDK that already handles auth, refresh, base URL, error normalization, retries — everything.

import { mySDK } from './sdk';

createUseGql({
  transport: async ({ query, variables, config }) => {
    return mySDK.graphql.request({ query, variables }, config);
  },
});

Selection DSL

include — what to fetch

include is the only selection key. Empty include is a type error — GraphQL has no "fetch everything".

gql.query({
  user: { id: 'u_1', include: { id: true, email: true } },
});

Nested selection composes the same way:

gql.query({
  user: {
    id: 'u_1',
    include: {
      id: true,
      posts: { include: { id: true, title: true } },
    },
  },
});

Wrapper unwrap (items / data)

Many servers wrap responses in a single-field serializer:

type FindUserResult { data: User }
type UsersPage { items: [User!]! }

gqlx detects these at codegen time and lets you select the inner type directly. The runtime keeps the wrapper shape so your result is still { data: { id: string } | null }.

gql.query({
  findUserById: {
    id: 'u_1',
    include: { id: true, email: true }, // not { data: { include: { … } } }
  },
});
// Emits:  findUserById(id: $v1) { data { id email } }

__field — aliasing

Call the same field with different arguments in one operation:

gql.query({
  admins:    { __field: 'users', where: { role: 'ADMIN' },    include: { id: true } },
  customers: { __field: 'users', where: { role: 'CUSTOMER' }, include: { id: true } },
});
// data.admins / data.customers — both typed

__on — unions & interfaces

gql.query({
  search: {
    where: { q: 'jane' },
    include: {
      __typename: true,
      __on: {
        User:    { id: true, email: true },
        Patient: { id: true, mrn: true },
      },
    },
  },
});

Array root — batched operations

Pass an array at the root and gqlx merges every part into one GraphQL operation — one round-trip, one cache key. Duplicate top-level keys throw at build time; disambiguate with __field.

gql.query([
  { context:  { settings: { where: { ... }, include: { id: true } } } },
  { identity: { users:    { where: { ... }, include: { id: true } } } },
]);

Per-call config

Any root field can carry a config object — it flows through to the underlying transport (an AxiosRequestConfig for the axios adapter, folded into URL + headers for fetch):

gql.query({
  findContextSetting: {
    filter: { query: {} },
    include: { id: true },
    config: {
      params:  { zone: 'client' },
      headers: { 'x-debug': '1' },
    },
  },
});

You can also pass signal and a top-level config to gql.query(selection, opts):

const controller = new AbortController();

await gql.query(
  { findUsers: { include: { id: true } } },
  { signal: controller.signal, config: { timeout: 5_000 } },
);

Variables

Every argument is lifted to a typed $variable. Never inline user data into a query — gqlx handles it. JSON scalars pass through verbatim; do not pre-JSON.stringify(props).

CLI

gqlx init                       Scaffold gql.config.ts in the current directory
gqlx generate [--config <path>] Introspect endpoints and emit the typed client

Options:
  --config <path>   Path to config (default: ./gql.config.ts | .js | .mjs)
  --cwd <path>      Working directory (default: process.cwd())
  -h, --help        Show this help

Wire it into package.json so contributors get the same output:

{
  "scripts": {
    "gql:gen": "gqlx generate",
    "postinstall": "gqlx generate"
  }
}

Programmatic codegen

For build scripts that need finer control than the CLI offers:

import { introspect, mergeIntrospections, emit } from 'gqlx/codegen';

const parts = await Promise.all([
  introspect('http://localhost:3010/graphql'),
  introspect({ url: 'http://localhost:6050/graphql', headers: { 'x-internal': '1' } }),
]);

const schema = mergeIntrospections(parts);

emit({
  schema,
  outDir: './src/generated/gqlx',
  runtimePackage: 'gqlx',
});

Custom output location

If you'd rather commit the generated code (review-able diffs, no postinstall step), set output in the config:

export default defineConfig({
  schemas: ['...'],
  output: './src/generated/gqlx',
});

Then import from your own path instead of the subpath:

import { createUseGql } from './generated/gqlx';

How gqlx/generated resolves

By default gqlx generate writes to node_modules/.gqlx/. The package's gqlx/generated subpath is a thin shim that re-exports from .gqlx, and Node's module resolution walks up the tree to find it. Same trick Prisma uses for @prisma/client. Custom output paths bypass this entirely — you import directly from wherever you wrote them.

Caveats

  • TypeScript recursion depth. Selected<T, S> fans out per field. For very large schemas (thousands of types) you may hit TS2589; split the output per domain in that case.
  • No subscriptions yet. The builder is shaped for it, but the transport layer doesn't ship a WebSocket adapter. Use a transport: ... function for now.
  • No fragment masking. If you need @graphql-codegen/client-preset-style colocation, run both side-by-side — they don't conflict.

License

MIT © gqlx contributors