aboutsummaryrefslogtreecommitdiff

@eol/gq

A batteries-included GraphQL toolkit for Deno, wrapping graphql-js and @graphql-tools/schema with the bits most APIs end up reaching for anyway: a Fetch-compatible HTTP handler, a cached gql tag, a .graphql file loader, and an embedded GraphiQL UI powered by @eeeooolll/graphiql.

Install

Published on JSR as @eol/gq.

deno add jsr:@eol/gq

Or import directly:

import { executeSchema, gql, GraphQLHTTP } from "jsr:@eol/gq";

Quick start

import { executeSchema, gql, GraphQLHTTP } from "@eol/gq";

const schema = executeSchema({
  resolvers: {
    Query: {
      hello: (_, { name }) => `hello, ${name ?? "world"}`
    }
  },
  typeDefs: gql`
    type Query {
      hello(name: String): String
    }
  `
});

Deno.serve(
  { port: 8000 },
  GraphQLHTTP({ graphiql: true, schema })
);

Visit http://localhost:8000 in a browser for GraphiQL, or POST a query to the same URL.

Loading schemas from .graphql files

Use importQL to read an entry file and resolve its imports into a single SDL string.

Explicit imports

# schema/schema.graphql
# import "./post.graphql"
# import "./user.graphql"

type Query {
  me: User
  posts: [Post!]!
}
import { executeSchema, gql, importQL } from "@eol/gq";

const typeDefs = gql(await importQL("schema/schema.graphql"));
const schema = executeSchema({ resolvers, typeDefs });

Dynamic imports

Drop # DYNAMIC_IMPORTS in your entry file and every .graphql file found one level below <cwd>/schema/ gets inlined at that marker:

schema/
  schema.graphql   # contains: # DYNAMIC_IMPORTS
  post/
    post.graphql
  user/
    user.graphql

API

All symbols are re-exported from the package root (@eol/gq).

GraphQLHTTP(options)

Returns a (request) => Promise<Response> handler. Pluggable into Deno.serve, oak, hono, or anything else Fetch-shaped.

Options:

Option Type Description
schema GraphQLSchema Required. Executable schema.
context (req) => Ctx \| Promise<Ctx> Builds the resolver context per request.
graphiql boolean Serve GraphiQL on GET + Accept: text/html.
headers HeadersInit Extra headers merged into every response.
playgroundOptions Omit<RenderPageOptions, "endpoint"> Passthrough options for the GraphiQL renderer.

executeSchema(config)

Re-export of @graphql-tools/schema’s makeExecutableSchema, renamed for brevity.

gql (tagged template)

Parses a GraphQL string into a DocumentNode. Results are cached by normalized source, and embedded DocumentNode interpolations are inlined from their original source.

Companion knobs:

  • disableExperimentalFragmentVariables()
  • disableFragmentWarnings()
  • enableExperimentalFragmentVariables()
  • resetCaches()

GraphQLWS(options)

Returns a (request: Request) => Response handler that upgrades incoming requests to a WebSocket and speaks the graphql-transport-ws protocol. Pass it as subscriptions to GraphQLHTTP so HTTP and WS share one endpoint.

Options:

Option Type Description
context (ctx) => Ctx \| Promise<Ctx> Builds the resolver context per WS connection.
schema GraphQLSchema Required. Executable schema.
... Partial<ServerOptions> Anything else graphql-ws accepts (onConnect, onSubscribe, …).

PubSub

Re-export of graphql-subscriptions's in-memory pub/sub. Resolvers call pubsub.asyncIterator(["EVENT"]) for subscribe; mutations call pubsub.publish("EVENT", payload). For multi-process deployments swap in graphql-redis-subscriptions — same PubSubEngine interface.

importQL(path)

Reads a .graphql file and resolves its imports. See Loading schemas from .graphql files.

runHttpQuery(params, options, request)

Low-level executor that GraphQLHTTP delegates to. Use it if you’re rolling your own transport but still want the context wiring.

Types

GQLOptions, GQLRequest, GraphQLParams, GraphQLHandler, plus RenderPageOptions for the GraphiQL shell.

We also export commonly imported GraphQL types: DocumentNode, ExecutionResult, GraphQLArgs, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, GraphQLTypeResolver, and Source.

Features

  • Import *.graphql files — explicit and dynamic — via importQL.
  • Embedded GraphiQL via @eeeooolll/graphiql — Svelte 5, CodeMirror 6, served as a prebuilt IIFE bundle from jsDelivr.
  • Ships typed; passes deno check entry.ts with no fuss.
  • Zero build step — it’s Deno, you just import it.

GraphiQL renderer options

playgroundOptions is forwarded to the HTML shell builder. All fields are optional:

Field Type Description
cdnUrl string CDN base. Defaults to //cdn.jsdelivr.net/npm.
faviconUrl string \| null null skips the favicon; undefined uses the bundle's default.
title string Document <title>. Defaults to "GraphiQL".
version string Pin @eeeooolll/graphiql to a specific version on the CDN. Recommended.

Pinning version is recommended — without it the shell hits jsDelivr's @latest cache, which can lag behind new releases by ~12h.

Subscriptions

Pair GraphQLWS with a PubSub instance from graphql-subscriptions to serve subscriptions over WebSockets on the same endpoint as HTTP:

import {
  executeSchema,
  gql,
  GraphQLHTTP,
  GraphQLWS,
  PubSub
} from "@eol/gq";

const pubsub = new PubSub();

const schema = executeSchema({
  resolvers: {
    Mutation: {
      ping: (_, { msg }) => {
        pubsub.publish("PING", { pinged: msg });
        return msg;
      }
    },
    Subscription: {
      pinged: { subscribe: () => pubsub.asyncIterator(["PING"]) }
    }
  },
  typeDefs: gql`
    type Mutation { ping(msg: String!): String }
    type Subscription { pinged: String }
  `
});

const subscriptions = GraphQLWS({ schema });

Deno.serve(
  { port: 4000 },
  GraphQLHTTP({
    graphiql: true,
    schema,
    subscriptions
  })
);

Clients connect WebSockets to the same URL using the graphql-transport-ws subprotocol. The bundled GraphiQL detects subscription operations and switches transports automatically. See subscription-example.ts for a runnable demo.

License

MIT