Gathering detailed insights and metrics for graphql-helix
Gathering detailed insights and metrics for graphql-helix
Gathering detailed insights and metrics for graphql-helix
Gathering detailed insights and metrics for graphql-helix
npm install graphql-helix
Typescript
Module System
Node Version
NPM Version
graphql-helix@1.13.0
Updated on Jul 09, 2022
graphql-helix@1.12.0
Updated on Mar 08, 2022
graphql-helix@1.11.0
Updated on Dec 16, 2021
graphql-helix@1.10.3
Updated on Nov 25, 2021
graphql-helix@1.10.2
Updated on Nov 19, 2021
graphql-helix@1.10.1
Updated on Nov 18, 2021
TypeScript (96.48%)
CSS (2.48%)
JavaScript (1.04%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
829 Stars
252 Commits
47 Forks
20 Watchers
50 Branches
30 Contributors
Updated on Apr 25, 2025
Latest Version
1.13.0
Package Id
graphql-helix@1.13.0
Unpacked Size
2.31 MB
Size
659.63 kB
File Count
70
NPM Version
8.11.0
Node Version
16.15.1
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
A highly evolved GraphQL HTTP Server 🧬
GraphQL Helix is a collection of utility functions for building your own GraphQL HTTP server. You can check out Building a GraphQL server with GraphQL Helix on DEV for a detailed tutorial on getting started.
@defer
and @stream
directives.graphql-js
.npm install graphql-helix
The following example shows how to integrate GraphQL Helix with Node.js using Express. This example shows how to implement all the basic features, including a GraphiQL interface, subscriptions and support for @stream
and @defer
. See the rest of the examples for implementations using other frameworks and runtimes. For implementing additional features, see the Recipes section below.
1import express, { RequestHandler } from "express"; 2import { 3 getGraphQLParameters, 4 processRequest, 5 renderGraphiQL, 6 shouldRenderGraphiQL, 7} from "../lib"; 8import { schema } from "./schema"; 9 10const app = express(); 11 12app.use(express.json()); 13 14app.use("/graphql", async (req, res) => { 15 // Create a generic Request object that can be consumed by Graphql Helix's API 16 const request = { 17 body: req.body, 18 headers: req.headers, 19 method: req.method, 20 query: req.query, 21 }; 22 23 // Determine whether we should render GraphiQL instead of returning an API response 24 if (shouldRenderGraphiQL(request)) { 25 res.send(renderGraphiQL()); 26 } else { 27 // Extract the GraphQL parameters from the request 28 const { operationName, query, variables } = getGraphQLParameters(request); 29 30 // Validate and execute the query 31 const result = await processRequest({ 32 operationName, 33 query, 34 variables, 35 request, 36 schema, 37 }); 38 39 // processRequest returns one of three types of results depending on how the server should respond 40 // 1) RESPONSE: a regular JSON payload 41 // 2) MULTIPART RESPONSE: a multipart response (when @stream or @defer directives are used) 42 // 3) PUSH: a stream of events to push back down the client for a subscription 43 if (result.type === "RESPONSE") { 44 // We set the provided status and headers and just the send the payload back to the client 45 result.headers.forEach(({ name, value }) => res.setHeader(name, value)); 46 res.status(result.status); 47 res.json(result.payload); 48 } else if (result.type === "MULTIPART_RESPONSE") { 49 // Indicate we're sending a multipart response 50 res.writeHead(200, { 51 Connection: "keep-alive", 52 "Content-Type": 'multipart/mixed; boundary="-"', 53 "Transfer-Encoding": "chunked", 54 }); 55 56 // If the request is closed by the client, we unsubscribe and stop executing the request 57 req.on("close", () => { 58 result.unsubscribe(); 59 }); 60 61 res.write("---"); 62 63 // Subscribe and send back each result as a separate chunk. We await the subscribe 64 // call. Once we're done executing the request and there are no more results to send 65 // to the client, the Promise returned by subscribe will resolve and we can end the response. 66 await result.subscribe((result) => { 67 const chunk = Buffer.from(JSON.stringify(result), "utf8"); 68 const data = [ "", "Content-Type: application/json; charset=utf-8", "Content-Length: " + String(chunk.length), "", chunk, ]; 69 70 if (result.hasNext) { 71 data.push("---"); 72 } 73 74 res.write(data.join("\r\n")); 75 }); 76 77 res.write("\r\n-----\r\n"); 78 res.end(); 79 } else { 80 // Indicate we're sending an event stream to the client 81 res.writeHead(200, { 82 "Content-Type": "text/event-stream", 83 Connection: "keep-alive", 84 "Cache-Control": "no-cache", 85 }); 86 87 // If the request is closed by the client, we unsubscribe and stop executing the request 88 req.on("close", () => { 89 result.unsubscribe(); 90 }); 91 92 // We subscribe to the event stream and push any new events to the client 93 await result.subscribe((result) => { 94 res.write(`data: ${JSON.stringify(result)}\n\n`); 95 }); 96 } 97 } 98}); 99 100const port = process.env.PORT || 4000; 101 102app.listen(port, () => { 103 console.log(`GraphQL server is running on port ${port}.`); 104});
getGraphQLParameters
1function getGraphQLParameters(request: Request): GraphQLParams;
Extracts the query
, variables
and operationName
values from the request.
processRequest
1function processRequest<TContext, TRootValue>( 2 options: ProcessRequestOptions<TContext, TRootValue> 3): Promise<ProcessRequestResult<TContext, TRootValue>>;
Takes the schema
, request
, query
, variables
, operationName
and a number of other optional parameters and returns one of three kinds of results, depending on the sort of response the server should send back.
renderGraphiQL
1function renderGraphiQL(options: RenderGraphiQLOptions = {}): string;
Returns the HTML to render a GraphiQL instance.
shouldRenderGraphiQL
1function shouldRenderGraphiQL(request: Request): boolean;
Uses the method and headers in the request to determine whether a GraphiQL instance should be returned instead of processing an API request.
1export interface GraphQLParams { 2 operationName?: string; 3 query?: string; 4 variables?: string | { [name: string]: any }; 5} 6 7export interface RenderGraphiQLOptions { 8 /** 9 * An optional GraphQL string to use when no query is provided and no stored 10 * query exists from a previous session. If undefined is provided, GraphiQL 11 * will use its own default query. 12 */ 13 defaultQuery?: string; 14 /** 15 * Whether to open the variable editor by default. Defaults to `true`. 16 */ 17 defaultVariableEditorOpen?: boolean; 18 /** 19 * The endpoint requests should be sent. Defaults to `"/graphql"`. 20 */ 21 endpoint?: string; 22 /** 23 * The initial headers to render inside the header editor. Defaults to `"{}"`. 24 */ 25 headers?: string; 26 /** 27 * Whether the header editor is enabled. Defaults to `true`. 28 */ 29 headerEditorEnabled?: boolean; 30 /** 31 * A cryptographic nonce for use with Content-Security-Policy. 32 */ 33 nonce?: string; 34 /** 35 * The endpoint subscription requests should be sent to. Defaults to the value of the `endpoint` parameter. 36 */ 37 subscriptionsEndpoint?: string; 38} 39 40export interface ProcessRequestOptions<TContext, TRootValue> { 41 /** 42 * A function whose return value is passed in as the `context` to `execute`. 43 */ 44 contextFactory?: ( 45 executionContext: ExecutionContext 46 ) => Promise<TContext> | TContext; 47 /** 48 * An optional function which will be used to execute instead of default `execute` from `graphql-js`. 49 */ 50 execute?: typeof execute; 51 /** 52 * The name of the Operation in the Document to execute. 53 */ 54 operationName?: string; 55 /** 56 * An optional function which will be used to create a document instead of the default `parse` from `graphql-js`. 57 */ 58 parse?: typeof parse; 59 /** 60 * A Document containing GraphQL Operations and Fragments to execute. 61 */ 62 query?: string | DocumentNode; 63 /** 64 * An object describing the HTTP request. 65 */ 66 request: Request; 67 /** 68 * A function whose return value is passed in as the `rootValue` to `execute`. 69 */ 70 rootValueFactory?: ( 71 executionContext: ExecutionContext 72 ) => Promise<TRootValue> | TRootValue; 73 /** 74 * The GraphQL schema used to process the request. 75 */ 76 schema: GraphQLSchema; 77 /** 78 * An optional function which will be used to subscribe instead of default `subscribe` from `graphql-js`. 79 */ 80 subscribe?: typeof subscribe; 81 /** 82 * An optional function which will be used to validate instead of default `validate` from `graphql-js`. 83 */ 84 validate?: typeof validate; 85 /** 86 * An optional array of validation rules that will be applied to the document 87 * in place of those defined by the GraphQL specification. 88 */ 89 validationRules?: ReadonlyArray<ValidationRule>; 90 /** 91 * Values for any Variables defined by the Operation. 92 */ 93 variables?: string | { [name: string]: any }; 94} 95 96export interface ExecutionContext { 97 document: DocumentNode; 98 operation: OperationDefinitionNode; 99 variables?: { readonly [name: string]: unknown }; 100} 101 102export interface Request { 103 body?: any; 104 headers: Headers; 105 method: string; 106 query: any; 107} 108 109export type Headers = 110 | Record<string, string | string[] | undefined> 111 | { get(name: string): string | null }; 112 113export interface Response<TContext, TRootValue> { 114 type: "RESPONSE"; 115 status: number; 116 headers: { name: string; value: string }[]; 117 payload: ExecutionResult; 118 context?: TContext; 119 rootValue?: TRootValue; 120 document?: DocumentNode; 121 operation?: OperationDefinitionNode; 122} 123 124export interface MultipartResponse<TContext, TRootValue> { 125 type: "MULTIPART_RESPONSE"; 126 subscribe: (onResult: (result: ExecutionResult) => void) => Promise<void>; 127 unsubscribe: () => void; 128 context?: TContext; 129 rootValue?: TRootValue; 130 document?: DocumentNode; 131 operation?: OperationDefinitionNode; 132} 133 134export interface Push<TContext, TRootValue> { 135 type: "PUSH"; 136 subscribe: (onResult: (result: ExecutionResult) => void) => Promise<void>; 137 unsubscribe: () => void; 138 context?: TContext; 139 rootValue?: TRootValue; 140 document?: DocumentNode; 141 operation?: OperationDefinitionNode; 142} 143 144export type ProcessRequestResult<TContext, TRootValue> = 145 | Response<TContext, TRootValue> 146 | MultipartResponse<TContext, TRootValue> 147 | Push<TContext, TRootValue>;
extensions
field to the response with additional metadata to send to the clientSee here for a basic example of error handling.
1app.use("/graphql", async (req, res) => {
2 ...
3
4 const result = await processRequest({
5 operationName,
6 query,
7 variables,
8 request,
9 schema,
10 contextFactory: () => ({
11 req,
12 }),
13 });
14}
The contextFactory
can be asyncronous and return a Promise. The function is called with a single parameter, an object with the following properties:
1export interface ExecutionContext { 2 document: DocumentNode; 3 operation: OperationDefinitionNode; 4 variables?: { readonly [name: string]: unknown }; 5}
GraphQL Helix provides this information to contextFactory
in case you want to modify the context based on the operation that will be executed.
With contextFactory
, we have a mechanism for doing authentication and authorization inside our application. We can determine who is accessing our API and capture that information inside the context. Our resolvers can then use the context to determine whether a particular field can be resolved and how to resolve it. Check out this example for basic contextFactory
usage. If you're looking for a robust authorization solution, I highly recommend GraphQL Shield.
When the operation being executed is a subscription, processRequest
will return a PUSH
result, which you can then use to return a text/event-stream
response. Here's what a basic implementation looks like:
1if (result.type === "PUSH") { 2 // Indicate that we're sending a stream of events and should keep the connection open. 3 res.writeHead(200, { 4 "Content-Type": "text/event-stream", 5 Connection: "keep-alive", 6 "Cache-Control": "no-cache", 7 }); 8 9 // If the client closes the connection, we unsubscribe to prevent memory leaks. 10 req.on("close", () => { 11 result.unsubscribe(); 12 }); 13 14 // We subscribe to any new events and push them down to the client that initiated the subscription. 15 await result.subscribe((result) => { 16 res.write(`data: ${JSON.stringify(result)}\n\n`); 17 }); 18}
On the client-side, we use the EventSource API to listen to these events. Our EventSource instance should reconnect in the event the connection is closed, but this behavior varies widely from browser to browser. Therefore, it's a good idea to implement a keep-alive mechanism in production to ensure your connection stays persistent. Check out this StackOverflow post for additional details. On the back end, you can just use setInterval
to periodically send the keep alive message to the client (just make sure to clear the timer when you unsubscribe
).
Implementing SSE on the client-side is equally simple, but you can use sse-z to make it even easier. If you're adding keep-alive to your implementation, sse-z
provides a nice abstraction for that as well.
If SSE is not your cup of tea and you want to use WebSocket as the transport for your subscriptions instead, you can still do that. For example, we can use both GraphQL Helix and graphql-ws
1import express from "express"; 2import { 3 getGraphQLParameters, 4 processRequest, 5 renderGraphiQL, 6 shouldRenderGraphiQL, 7} from "graphql-helix"; 8import { execute, subscribe } from "graphql"; 9import { createServer } from "graphql-ws"; 10import { schema } from "./schema"; 11 12const app = express(); 13 14app.use(express.json()); 15 16app.use("/graphql", async (req, res) => { 17 // handle the request using processRequest as shown before 18}); 19 20const port = process.env.PORT || 4000; 21 22const server = app.listen(port, () => { 23 createServer( 24 { 25 schema, 26 execute, 27 subscribe, 28 }, 29 { 30 server, 31 path: "/graphql", 32 } 33 ); 34 35 console.log(`GraphQL server is running on port ${port}.`); 36});
A complete example can be found here. If you'd prefer you use socket.io, take a look at socket-io-graphql-server instead.
See here for an example.
1if (result.type === "MULTIPART_RESPONSE") { 2 // Indicate that this is a multipart response and the connection should be kept open. 3 res.writeHead(200, { 4 Connection: "keep-alive", 5 "Content-Type": 'multipart/mixed; boundary="-"', 6 "Transfer-Encoding": "chunked", 7 }); 8 9 // If the client closes the connection, we unsubscribe to prevent memory leaks. 10 req.on("close", () => { 11 result.unsubscribe(); 12 }); 13 14 res.write("---"); 15 16 // Subscribe to new results. The callback will be called with the 17 // ExecutionResult object that should be sent back to the client for each chunk. 18 await result.subscribe((result) => { 19 const chunk = Buffer.from(JSON.stringify(formatResult(result)), "utf8"); 20 const data = [ "Content-Type: application/json; charset=utf-8", "Content-Length: " + String(chunk.length), "", chunk, ]; 21 22 if (result.hasNext) { 23 data.push("---"); 24 } 25 26 res.write(data.join("\r\n")); 27 }); 28 29 // The Promise returned by `subscribe` will only resolve once all chunks have been emitted, 30 // at which point we can end the request. 31 res.write("\r\n-----\r\n"); 32 res.end(); 33}
See the here for a complete example.
The examples used in this repo are compatible with client-side libraries like meros and fetch-multipart-graphql.
With GraphQL Helix, it's as simple as adding the directive to your schema and utilizing the alternative execute
function provided by @n1ru4l/in-memory-live-query-store.
1import { InMemoryLiveQueryStore } from "@n1ru4l/in-memory-live-query-store";
2
3const liveQueryStore = new InMemoryLiveQueryStore();
4
5...
6
7const result = await processRequest({
8 operationName,
9 query,
10 variables,
11 request,
12 schema,
13 contextFactory: () => ({
14 liveQueryStore,
15 }),
16 execute: liveQueryStore.execute,
17});
You can checkout the complete example here.
The query
value that's passed to processQuery
can be an already-parsed DocumentNode object instead of a string. This lets us fetch the query from memory based on some other value, like a queryId
parameter. A rudimentary implementation could be as simple as this:
1let queryId: string; 2let operationName: string | undefined; 3let variables: any; 4 5if (req.method === "POST") { 6 queryId = req.body.queryId; 7 operationName = req.body.operationName; 8 variables = req.body.variables; 9} else { 10 queryId = req.query.queryId as string; 11 operationName = req.query.operationName as string; 12 variables = req.query.variables; 13} 14 15const query = queryMap[queryId]; 16 17if (!query) { 18 res.status(400); 19 res.json({ 20 errors: [ 21 new GraphQLError( 22 `Could not find a persisted query with an id of ${queryId}` 23 ), 24 ], 25 }); 26 return; 27} 28 29const result = await processRequest({ 30 operationName, 31 query, 32 variables, 33 request, 34 schema, 35});
See here for a more complete example. A more robust solution can be implemented using a library like relay-compiler-plus.
1const result = await processRequest({
2 // ...
3 execute: (
4 schema,
5 documentAst,
6 rootValue,
7 contextValue,
8 variableValues,
9 operationName
10 ) => {
11 const compiledQuery = compileQuery(schema, documentAst, operationName);
12
13 if (isCompiledQuery(compiledQuery)) {
14 return compiledQuery.query(rootValue, contextValue, variableValues || {});
15 }
16
17 return compiledQuery;
18 },
19});
⚠️ GraphQL JIT is an experimental library that is still lacking some features required by the GraphQL specification. You probably should not use it in production unless you know what you're getting yourself into.
The ability to provide custom implementations of parse
and validate
means we can also optimize the performance of those individual steps by introducing caching. This allows us to bypass these steps for queries we've processed before.
For example, we can create a simple in-memory cache
1import lru from "tiny-lru"; 2 3const cache = lru(1000, 3600000);
and then use it to cache our parsed queries so we can skip that step for subsequent requests:
1import { parse } from "graphql"; 2 3const result = await processRequest({ 4 operationName, 5 query, 6 variables, 7 request, 8 schema, 9 parse: (source, options) => { 10 if (!cache.get(query)) { 11 cache.set(query, parse(source, options)); 12 } 13 14 return cache.get(query); 15 }, 16});
We can take a similar approach with validate
and even cache the result of compileQuery
if we're using GraphQL JIT. See this example for a more complete implementation.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
branch protection is not maximal on development and all release branches
Details
Reason
Found 3/14 approved changesets -- score normalized to 2
Reason
project is archived
Details
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
69 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More