@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
*.graphqlfiles — explicit and dynamic — viaimportQL. - Embedded GraphiQL via
@eeeooolll/graphiql— Svelte 5, CodeMirror 6, served as a prebuilt IIFE bundle from jsDelivr. - Ships typed; passes
deno check entry.tswith 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