Skip to content

Repository files navigation

GraphQL Query Builder

A simple helper function to generate GraphQL queries using plain JavaScript Objects (JSON).

downloads CI status

Install

npm install gql-query-builder
# or
pnpm add gql-query-builder
# or
yarn add gql-query-builder

Requires Node.js >= 22 (works in browsers too). The package ships both ESM (import) and CommonJS (require) builds with TypeScript types included β€” no extra @types package needed.

Usage

import * as gql from 'gql-query-builder'

const query = gql.query(options: object)
const mutation = gql.mutation(options: object)
const subscription = gql.subscription(options: object)

You can also import query or mutation or subscription individually:

import  { query, mutation, subscription } from 'gql-query-builder'

query(options: object)
mutation(options: object)
subscription(options: object)

Options

options is { operation, fields, variables } or an array of options

Name Description Type Required Example
operation Name of operation to be executed on server String | Object Yes getThoughts, createThought

{ name: 'getUser', alias: 'getAdminUser' }
fields Selection of fields Array No ['id', 'name', 'thought']

['id', 'name', 'thought', { user: ['id', 'email'] }]
variables Variables sent to the operation Object No { key: value } eg: { id: 1 }

{ key: { value: value, required: true, type: GQL type, list: true, name: argument name } eg:
{ email: { value: "user@example.com", required: true }, password: { value: "123456", required: true }, secondaryEmails: { value: [], required: false, type: 'String', list: true, name: secondaryEmail } }

Adapter

An optional second argument adapter is a typescript/javascript class that implements the QueryAdapter, MutationAdapter, or SubscriptionAdapter interface (exported from the package root).

If adapter is undefined then the default adapter (DefaultQueryAdapter, DefaultMutationAdapter, or DefaultSubscriptionAdapter β€” also exported for reuse) is used.

import * as gql from 'gql-query-builder'

const query = gql.query(options: object, adapter?: MyCustomQueryAdapter, config?: object)
const mutation = gql.mutation(options: object, adapter?: MyCustomMutationAdapter, config?: object)
const subscription = gql.subscription(options: object, adapter?: MyCustomSubscriptionAdapter, config?: object)

Config

Name Description Type Required Example
operationName Name of operation to be sent to the server String No getThoughts, createThought

Examples

  1. Query
  2. Query (with variables)
  3. Query (with nested fields selection)
  4. Query (with required variables)
  5. Query (with custom argument name)
  6. Query (with operation name)
  7. Query (with empty fields)
  8. Query (with alias)
  9. Query (with inline fragment)
  10. Query (with adapter defined)
  11. Mutation
  12. Mutation (with required variables)
  13. Mutation (with custom types)
  14. Mutation (with adapter defined)
  15. Mutation (with operation name)
  16. Subscription
  17. Subscription (with adapter defined)
  18. Query (with default variable values)
  19. Query (with named fragments)
  20. Query (with directives)
  21. Subscription (with operation name)
  22. Example with Fetch
  23. Example with Axios

Query:

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: 'thoughts',
  fields: ['id', 'name', 'thought']
})

console.log(query)

// Output
query {
  thoughts {
    id,
    name,
    thought
  }
}

↑ all examples

Query (with variables):

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: 'thought',
  variables: { id: 1 },
  fields: ['id', 'name', 'thought']
})

console.log(query)

// Output
query ($id: Int) {
  thought (id: $id) {
    id, name, thought
  }
}

// Variables
{ "id": 1 }

↑ all examples

Query (with nested fields selection):

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: 'orders',
  fields: [
    'id',
    'amount',
    {
     user: [
        'id',
        'name',
        'email',
        {
          address: [
            'city',
            'country'
          ]
        }
      ]
    }
  ]
})

console.log(query)

// Output
query {
  orders  {
    id,
    amount,
    user {
      id,
      name,
      email,
      address {
        city,
        country
      }
    }
  }
}

↑ all examples

Query (with required variables):

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: 'userLogin',
  variables: {
    email: { value: 'jon.doe@example.com', required: true },
    password: { value: '123456', required: true }
  },
  fields: ['userId', 'token']
})

console.log(query)

// Output
query ($email: String!, $password: String!) {
  userLogin (email: $email, password: $password) {
    userId, token
  }
}

// Variables
{
  email: "jon.doe@example.com",
  password: "123456"
}

↑ all examples

Query (with custom argument name):

import * as gql from 'gql-query-builder'

const query = gql.query([{
  operation: "someoperation",
  fields: [{
    operation: "nestedoperation",
    fields: ["field1"],
    variables: {
      id2: {
        name: "id",
        type: "ID",
        value: 123,
      },
    },
  }, ],
  variables: {
    id1: {
      name: "id",
      type: "ID",
      value: 456,
    },
  },
}, ]);

console.log(query)

// Output
query($id2: ID, $id1: ID) {
  someoperation(id: $id1) {
    nestedoperation(id: $id2) {
      field1
    }
  }
}

// Variables
{
  "id1": 456,
  "id2": 123
}

↑ all examples

Query (with operation name):

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: 'userLogin',
  fields: ['userId', 'token']
}, null, {
  operationName: 'someoperation'
})

console.log(query)

// Output
query someoperation {
  userLogin {
    userId
    token
  }
}

↑ all examples

Query (with empty fields):

import * as gql from 'gql-query-builder'

const query = gql.query([
  { operation: "getFilteredUsersCount" },
  { operation: "getAllUsersCount", fields: [] },
  { operation: "getFilteredUsers", fields: [{ count: [] }] },
]);

console.log(query)

// Output
query {
  getFilteredUsersCount
  getAllUsersCount
  getFilteredUsers {
    count
  }
}

↑ all examples

Query (with alias):

import * as gql from 'gql-query-builder'

const query = gql.query({
  operation: {
    name: 'thoughts',
    alias: 'myThoughts',
  },
  fields: ['id', 'name', 'thought']
})

console.log(query)

// Output
query {
  myThoughts: thoughts {
    id,
    name,
    thought
  }
}

↑ all examples

Query (with inline fragment):

import * as gql from 'gql-query-builder'

const query = gql.query({
    operation: "thought",
    fields: [
        "id",
        "name",
        "thought",
        {
            operation: "FragmentType",
            fields: ["emotion"],
            fragment: true,
        },
    ],
});

console.log(query)

// Output
query {
    thought {
        id,
        name,
        thought,
        ... on FragmentType {
            emotion
        }
    }
}

↑ all examples

Query (with adapter defined):

For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.

import * as gql from 'gql-query-builder'
import MyQueryAdapter from 'where/adapters/live/MyQueryAdapter'

const query = gql.query({
  operation: 'thoughts',
  fields: ['id', 'name', 'thought']
}, MyQueryAdapter)

console.log(query)

// Output
query SomethingIDidInMyAdapter {
  thoughts {
    id,
    name,
    thought
  }
}

Take a peek at DefaultQueryAdapter to get an understanding of how to make a new adapter.

↑ all examples

Mutation:

import * as gql from 'gql-query-builder'

const query = gql.mutation({
  operation: 'thoughtCreate',
  variables: {
    name: 'Tyrion Lannister',
    thought: 'I drink and I know things.'
  },
  fields: ['id']
})

console.log(query)

// Output
mutation ($name: String, $thought: String) {
  thoughtCreate (name: $name, thought: $thought) {
    id
  }
}

// Variables
{
  "name": "Tyrion Lannister",
  "thought": "I drink and I know things."
}

↑ all examples

Mutation (with required variables):

import * as gql from 'gql-query-builder'

const query = gql.mutation({
  operation: 'userSignup',
  variables: {
    name: { value: 'Jon Doe' },
    email: { value: 'jon.doe@example.com', required: true },
    password: { value: '123456', required: true }
  },
  fields: ['userId']
})

console.log(query)

// Output
mutation ($name: String, $email: String!, $password: String!) {
  userSignup (name: $name, email: $email, password: $password) {
    userId
  }
}

// Variables
{
  name: "Jon Doe",
  email: "jon.doe@example.com",
  password: "123456"
}

↑ all examples

Mutation (with custom types):

import * as gql from 'gql-query-builder'

const query = gql.mutation({
  operation: "userPhoneNumber",
  variables: {
    phone: {
      value: { prefix: "+91", number: "9876543210" },
      type: "PhoneNumber",
      required: true
    }
  },
  fields: ["id"]
})

console.log(query)

// Output
mutation ($phone: PhoneNumber!) {
  userPhoneNumber (phone: $phone) {
    id
  }
}

// Variables
{
  phone: {
    prefix: "+91", number: "9876543210"
  }
}

↑ all examples

Mutation (with adapter defined):

For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.

import * as gql from 'gql-query-builder'
import MyMutationAdapter from 'where/adapters/live/MyQueryAdapter'

const query = gql.mutation({
  operation: 'thoughts',
  fields: ['id', 'name', 'thought']
}, MyMutationAdapter)

console.log(query)

// Output
mutation SomethingIDidInMyAdapter {
  thoughts {
    id,
    name,
    thought
  }
}

↑ all examples

Take a peek at DefaultMutationAdapter to get an understanding of how to make a new adapter.

Mutation (with operation name):

import * as gql from 'gql-query-builder'

const query = gql.mutation({
  operation: 'thoughts',
  fields: ['id', 'name', 'thought']
}, undefined, {
  operationName: 'someoperation'
})

console.log(query)

// Output
mutation someoperation {
  thoughts {
    id
    name
    thought
  }
}

↑ all examples

Subscription:

import { subscription } from 'gql-query-builder'

const sub = subscription({
  operation: 'thoughtCreate',
  variables: {
    name: 'Tyrion Lannister',
    thought: 'I drink and I know things.'
  },
  fields: ['id']
})

console.log(sub)

// Output
subscription ($name: String, $thought: String) {
  thoughtCreate (name: $name, thought: $thought) {
    id
  }
}

// Variables
{
  "name": "Tyrion Lannister",
  "thought": "I drink and I know things."
}

↑ all examples

Subscription (with adapter defined):

For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.

import * as gql from 'gql-query-builder'
import MySubscriptionAdapter from 'where/adapters/live/MyQueryAdapter'

const query = gql.subscription({
  operation: 'thoughts',
  fields: ['id', 'name', 'thought']
}, MySubscriptionAdapter)

console.log(query)

// Output
subscription SomethingIDidInMyAdapter {
  thoughts {
    id,
    name,
    thought
  }
}

Take a peek at DefaultSubscriptionAdapter to get an understanding of how to make a new adapter.

↑ all examples

Query (with default variable values):

Add a default to a variable descriptor to emit a GraphQL default value. Strings are quoted and input objects use unquoted keys automatically; wrap enum literals with rawGraphQL():

import { query, rawGraphQL } from 'gql-query-builder'

const q = query({
  operation: 'hero',
  variables: {
    episode: { value: 'EMPIRE', type: 'Episode', default: rawGraphQL('JEDI') },
    first: { value: 10, default: 5 }
  },
  fields: ['name']
})

// Output
query ($episode: Episode = JEDI, $first: Int = 5) {
  hero (episode: $episode, first: $first) {
    name
  }
}

↑ all examples

Query (with named fragments):

Define reusable named fragments with the fragments config option and spread them with a plain '...name' field string. Works for queries, mutations, and subscriptions:

import { query } from 'gql-query-builder'

const q = query({
  operation: 'hero',
  fields: ['...heroFields']
}, null, {
  fragments: [
    { name: 'heroFields', on: 'Character', fields: ['name', { friends: ['name'] }] }
  ]
})

// Output
query {
  hero {
    ...heroFields
  }
}

fragment heroFields on Character { name, friends { name } }

↑ all examples

Query (with directives):

Directives such as @include(if:) and @skip(if:) (or @defer/@stream where supported) can be attached to any field β€” field strings are passed through verbatim:

import { query } from 'gql-query-builder'

const q = query({
  operation: 'hero',
  variables: { withFriends: { value: true, type: 'Boolean', required: true } },
  fields: ['name', 'friends @include(if: $withFriends)']
})

// Output
query ($withFriends: Boolean!) {
  hero (withFriends: $withFriends) {
    name,
    friends @include(if: $withFriends)
  }
}

The same passthrough works for field-level aliases ('empireHero: name') and meta fields ('__typename').

↑ all examples

Subscription (with operation name):

import { subscription } from 'gql-query-builder'

const q = subscription({
  operation: 'postAdded',
  variables: { topic: 'news' },
  fields: ['id']
}, null, {
  operationName: 'OnPostAdded'
})

// Output
subscription OnPostAdded ($topic: String) {
  postAdded (topic: $topic) {
    id
  }
}

↑ all examples

Example with Fetch

No extra dependencies needed β€” fetch is built into modern browsers, Node.js >= 18, Deno, and Bun. Unlike Axios, you serialize the body and set the Content-Type header yourself.

Query:

import { query } from "gql-query-builder";

async function getThoughts() {
  try {
    const response = await fetch("http://api.example.com/graphql", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(
        query({
          operation: "thoughts",
          fields: ["id", "name", "thought"],
        })
      ),
    });

    const result = await response.json();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
}

↑ all examples

Mutation:

import { mutation } from "gql-query-builder";

async function saveThought() {
  try {
    const response = await fetch("http://api.example.com/graphql", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(
        mutation({
          operation: "thoughtCreate",
          variables: {
            name: "Tyrion Lannister",
            thought: "I drink and I know things.",
          },
          fields: ["id"],
        })
      ),
    });

    const result = await response.json();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
}

↑ all examples

Example with Axios

Query:

import axios from "axios";
import { query } from "gql-query-builder";

async function getThoughts() {
  try {
    const response = await axios.post(
      "http://api.example.com/graphql",
      query({
        operation: "thoughts",
        fields: ["id", "name", "thought"],
      })
    );

    console.log(response);
  } catch (error) {
    console.log(error);
  }
}

↑ all examples

Mutation:

import axios from "axios";
import { mutation } from "gql-query-builder";

async function saveThought() {
  try {
    const response = await axios.post(
      "http://api.example.com/graphql",
      mutation({
        operation: "thoughtCreate",
        variables: {
          name: "Tyrion Lannister",
          thought: "I drink and I know things.",
        },
        fields: ["id"],
      })
    );

    console.log(response);
  } catch (error) {
    console.log(error);
  }
}

↑ all examples

πŸ› Interactive playground

Prefer to learn by poking at it? example/ is a runnable single-page app that turns every feature below into a live, editable recipe β€” nested selections, aliases, fragments, the full variable-descriptor vocabulary, mutations, subscriptions and custom adapters. The query recipes run against a real GraphQL server (the public Rick & Morty API), so you can watch the generated string come back with real data.

cd example
pnpm install && pnpm dev   # β†’ http://localhost:5173

Development

This repo uses pnpm, Biome for lint/format, Vitest for tests, tsup for the dual ESM/CJS build, and Changesets for releases.

pnpm install       # install dependencies
pnpm test          # run the test suite
pnpm lint          # lint + format check
pnpm typecheck     # TypeScript, no emit
pnpm build         # build ESM + CJS + types into dist/
pnpm changeset     # describe your change for the next release

Every user-facing PR should include a changeset. Merging to main opens/updates an automated "Version Packages" PR; merging that publishes to npm. Publishing uses npm trusted publishing (OIDC).

A compact, machine-friendly API reference lives in docs/api.md; agent/LLM entry points are AGENTS.md and llms.txt.

AI disclosure

Parts of this project were written with the help of AI tools.

License

The MIT License (http://www.opensource.org/licenses/mit-license.php)

About

πŸ”§ Simple GraphQL Query Builder

Topics

Resources

Stars

410 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages