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

@lewebsimple/nuxt-graphql

v0.6.16

Published

Opinionated Nuxt module for using GraphQL

Readme

Nuxt GraphQL

npm version npm downloads License Nuxt

Opinionated Nuxt module that wires a typed GraphQL server + client into your app.

✨  Release Notes

Features

  • 🧘 GraphQL Yoga server at /api/graphql (GraphiQL in dev) + SSE subscriptions
  • 🪡 Stitched schema from local and/or remote schemas (remote introspection at build time; subscriptions stripped)
  • 🪄 Code generation from .gql documents → typed operation documents + registry
  • 🧠 Type-safe helpers for queries, mutations, and subscriptions, shared across client + server
  • 🧊 SSR-friendly by default: request header forwarding + server-side schema execution helpers
  • 🚀 Client-side cache for useAsyncGraphQLQuery (cache policies + optional persistence in localStorage)
  • 🧯 Unified error model via GraphQLExecutionResult and NormalizedError

Getting started

Install the module to your Nuxt application with one command:

pnpx nuxt module add @lewebsimple/nuxt-graphql

Configuration

Declare your schemas, context, documents glob and optional client cache in nuxt.config.ts:

export default defineNuxtConfig({
  modules: ["@lewebsimple/nuxt-graphql"],
  graphql: {
    server: {
      // Schemas to stitch together (local and/or remote)
      schema: {
        // Local schema example
        local: { type: "local", path: "server/graphql/schema.ts" },
      
        // Remote schema example
        swapi: {
          type: "remote",
          url: "https://swapi-graphql.netlify.app/graphql",
          // Optional: static headers for this remote
          headers: {
            "X-Static-Header": "static-header-value",
          },
          // Optional: per-remote execution hooks
          hooks: ["server/graphql/swapi-hooks.ts"],
        },
      },

      // Optional: custom GraphQL context factories (defaults to [])
      context: ["server/graphql/context.ts"],
    },

    client: {
      // Optional: documents glob (defaults to **/*.gql)
      documents: "**/*.gql",

      // Optional: headers forwarded from SSR to graphql-request (defaults to ["authorization", "cookie"])
      ssrForwardHeaders: ["authorization", "cookie"],

      // Optional: query caching (client-side only, for useAsyncGraphQLQuery)
      cache: {
        policy: "cache-first", // "no-cache" | "cache-first" | "network-first" | "swr"
        keyVersion: "1",
        keyPrefix: "gql",
        // Persist cache entries in localStorage with TTL in seconds
        // - 0 = never expires
        // - undefined = persistence disabled
        ttl: 60,
      },
    },

    // Optional: save path for the stitched SDL (defaults to "server/graphql/schema.graphql")
    saveSDL: "server/graphql/schema.graphql",

    // Optional: save path for the generated GraphQL config (defaults to "graphql.config.json")
    saveConfig: "graphql.config.json",
  },
});

Define GraphQL schema (local and/or remote)

Local schemas must live under server/ and export a GraphQLSchema as schema.

For the example configuration above, create server/graphql/schema.ts:

import { createSchema } from "graphql-yoga";
import type { GraphQLContext } from "#graphql/context";

export const schema = createSchema<GraphQLContext>({
  typeDefs: /* GraphQL */ `
    type Query {
      hello: String!
    }
    type Mutation {
      ping(message: String!): String!
    }
    type Subscription {
      time: String!
    }
  `,
  resolvers: {
    Query: {
      hello: () => "Hello from Nuxt GraphQL!",
    },
    Mutation: {
      ping: (_parent, args) => `pong: ${args.message}`,
    },
    Subscription: {
      time: {
        subscribe: async function* () {
          while (true) {
            yield { time: new Date().toISOString() };
            await new Promise((r) => setTimeout(r, 1000));
          }
        },
      },
    },
  },
});

Remote schemas are introspected at build time from the endpoint URL and executed via an HTTP executor at runtime. Subscriptions are stripped from remote schemas.

The final schema is stitched from the all of the defined local / remote schemas.

Define GraphQL context (optional)

Context definition is optional and factories resolve in order on the server. Their return types are merged into a single GraphQLContext type which is exported from #graphql/context. You can use the auto-imported defineGraphQLContext helper for type-safety.

For example, create server/graphql/context.ts:

import { getUserSession } from "nuxt-auth-utils";

export default defineGraphQLContext(async (event) => {
  const session = await getUserSession(event);
  return {
    user: session?.user ?? null,
  };
});

Write GraphQL documents (.gql)

By default, the module scans **/*.gql files for named operations and fragments which are converted into types and typed document nodes in #graphql/operations. The operations are exposed by name in #graphql/registry to allow type-safe execution with the provided composables and server utils.

⚠️ Operation names are required and must be unique.

Example document files:

# app/graphql/HelloWorld.query.gql
query HelloWorld {
  hello
}
# app/graphql/Ping.mutation.gql
mutation Ping($message: String!) {
  ping(message: $message)
}
# app/graphql/Time.subscription.gql
subscription Time {
  time
}

That's it! You can now use Nuxt GraphQL in your Nuxt app ✨

Fragments

Fragments are fully supported and are the recommended way to share selection sets across operations.

  • Fragment names must be unique across all .gql files (duplicates throw during generation).
  • Fragment types are re-exported from #graphql/operations.
  • Fragments are not executable by themselves and are not part of the registry.

Example with a fragment:

# app/graphql/SwapiFilms.query.gql
fragment TheFilm on Film {
  title
  releaseDate
}

query SwapiFilms {
  allFilms {
    films {
      ...TheFilm
    }
  }
}

From TypeScript, you can also use fragment types explicitly when needed:

import type { TheFilmFragment } from "#graphql/operations";

Use the auto-imported composables

The auto-imported composables allow executing queries, mutations, and subscriptions based on their registry name with full type-safety (variables and return value).

// Cached query via useAsyncData
const { data, pending, error, refresh } = await useAsyncGraphQLQuery("HelloWorld", undefined);

// Direct HTTP query (SafeResult)
const { data: queryData, error: queryError } = await useGraphQLQuery("HelloWorld");

// Mutation (SafeResult)
const { mutate, pending: mutationPending } = useGraphQLMutation("Ping");
const { data: mutationData, error: mutationError } = await mutate({ message: "Hello!" });

// Subscription (client-only, SSE)
const { data, error, start, stop } = useGraphQLSubscription("Time");

Use the auto-imported server utils

In server routes, you can execute queries and mutations directly against the stitched schema (no HTTP roundtrip):

export default defineEventHandler(async (event) => {
  // Server-side GraphQL query example
  const { data: queryData, error: queryError } = await useGraphQLOperation(event, "HelloWorld" );

  // Server-side GraphQL mutation example
  const { data: mutationData } = await useGraphQLOperation(event, "Ping", { message: queryData?.hello ?? "Pong" },
  );

  return { queryData, mutationData, queryError };
});

Server helpers return a GraphQLExecutionResult in the same format as some composables, i.e. { data: TResult, error: null } | { data: null, error: NormalizedError }

Query caching (client-side only)

useAsyncGraphQLQuery can cache query results based on the global cache configuration and per-query overrides.

  • In-flight requests are deduplicated (same operation + variables → one network call).
  • In-memory cache uses Nuxt useAsyncData/useNuxtData.
  • Persisted cache stores entries in localStorage for ttl seconds (0 = never expires).

Cache policies

  • "no-cache": always fetches from the network (still dedupes in-flight).
  • "cache-first": returns cached value when present, otherwise fetches.
  • "network-first": tries the network first, falls back to cached value on error.
  • "swr": returns cached value immediately and refreshes in the background.

Per-query overrides

const { data } = await useAsyncGraphQLQuery("HelloWorld", undefined, {
  cache: {
    policy: "network-first",
    ttl: undefined, // disable persistence for this call
  },
});

Cache manipulation

On the client, useGraphQLCache() provides helpers to read, write, update, and invalidate cache entries:

const cache = useGraphQLCache();

// Read cached query (in-memory only)
const films = cache.read("AllFilms", {});

// Write cached query synchronously (in-memory only, useful for rollbacks)
cache.write("AllFilms", {}, newValue);
cache.write("AllFilms", {}, (current) => ({ ...current, films: [...current.films, newFilm] }));

// Update cached query asynchronously (in-memory + persisted)
await cache.update("AllFilms", {}, newValue);
await cache.update("AllFilms", {}, (current) => ({ ...current, films: [...current.films, newFilm] }));

// Invalidate cache entries
await cache.invalidate("HelloWorld", {});  // Exact match (operation + variables)
await cache.invalidate("HelloWorld");      // All entries for operation
await cache.invalidate();                  // All entries

⚠️ Important: Cache manipulation methods (read, write, update, invalidate) are incompatible with the transform option on useAsyncGraphQLQuery. If you need to use cache invalidation or manipulation, do not use the transform option. Instead, transform the data after retrieving it from the composable.

Optimistic updates

useGraphQLMutation supports optimistic updates via lifecycle hooks:

const { mutate } = useGraphQLMutation("AddFilm", {
  onMutate: async (variables) => {
    const cache = useGraphQLCache();
    
    // Snapshot current value for rollback
    const snapshot = cache.read("AllFilms", {});
    
    // Optimistically update cache
    await cache.update("AllFilms", {}, (current) => ({
      films: [...(current?.films ?? []), { id: 'temp', title: variables.title }]
    }));
    
    return { snapshot };
  },
  
  onError: (error, variables, context) => {
    const cache = useGraphQLCache();
    // Rollback on error (sync for instant UI update)
    if (context?.snapshot) {
      cache.write("AllFilms", {}, context.snapshot);
    }
  },
  
  onSuccess: (data, variables, context) => {
    // Replace optimistic temp ID with real ID from server
    const cache = useGraphQLCache();
    cache.update("AllFilms", {}, (current) => ({
      films: current?.films.map(f => f.id === 'temp' ? data.addFilm : f) ?? []
    }));
  },
  
  onSettled: (result, variables, context) => {
    // Always runs after mutation (success or error)
    console.log('Mutation completed');
  }
});

const result = await mutate({ title: "New Film" });

Remote executor hooks (optional, per remote)

You can define custom logic around the remote executor for each remote schema by using the auto-imported defineRemoteExecutorHooks helper.

All hooks receive the GraphQL context as a second parameter for convenient access.

For the example configuration above, create server/graphql/swapi-hooks.ts:

import { defu } from "defu";

export default defineRemoteExecutorHooks({
  onRequest(request, context) {
    // Context is available as second parameter
    const { remoteAuthToken } = context || {};
    request.extensions = defu(request.extensions, {
      headers: {
        "XAuthorization": `Bearer ${remoteAuthToken || ""}`,
      },
    });
  },
  
  onResult(result, context) {
    // You can also access context in onResult
    console.log("User from context:", context?.user);
    console.log("Result:", result.data);
  },
  
  onError(error, context) {
    // And in onError for logging/monitoring
    console.error("Remote execution failed for user:", context?.user?.id);
  },
});

Contribution

# Install dependencies
pnpm install

# Generate type stubs
pnpm run dev:prepare

# Develop with the playground
pnpm run dev

# Build the playground
pnpm run dev:build

# Run ESLint
pnpm run lint

# Run Vitest
pnpm run test
pnpm run test:watch

# Release new version
pnpm run release