Giter Site home page Giter Site logo

graphql-helix's Introduction


GraphQL Helix


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.

Features

  • Framework and runtime agnostic. Use whatever HTTP library you want. GraphQL Helix works in Node, Deno and in the browser.
  • HTTP first. GraphQL Helix allows you to create a GraphQL over HTTP specification-compliant server, while exposing a single HTTP endpoint for everything from documentation to subscriptions.
  • Server push and client pull. GraphQL Helix supports real-time requests with both subscriptions and @defer and @stream directives.
  • Flexible. GraphQL Helix abstracts away logic that's common to all GraphQL HTTP servers, while leaving the implementation to you. Implement the features you want and take full control of your transport layer.
  • Minimal. No bloat. No paid platform integration. Zero dependencies outside of graphql-js.

Installation

npm install graphql-helix
yarn add graphql-helix

Basic Usage

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.

import express, { RequestHandler } from "express";
import { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, sendResult } from "graphql-helix";
import { schema } from "./schema";

const app = express();

app.use(express.json());

app.use("/graphql", async (req, res) => {
  // Create a generic Request object that can be consumed by Graphql Helix's API
  const request = {
    body: req.body,
    headers: req.headers,
    method: req.method,
    query: req.query,
  };

  // Determine whether we should render GraphiQL instead of returning an API response
  if (shouldRenderGraphiQL(request)) {
    res.send(renderGraphiQL());
  } else {
    // Extract the Graphql parameters from the request
    const { operationName, query, variables } = getGraphQLParameters(request);

    // Validate and execute the query
    const result = await processRequest({
      operationName,
      query,
      variables,
      request,
      schema,
    });

    // processRequest returns one of three types of results depending on how the server should respond
    // 1) RESPONSE: a regular JSON payload
    // 2) MULTIPART RESPONSE: a multipart response (when @stream or @defer directives are used)
    // 3) PUSH: a stream of events to push back down the client for a subscription
    // The "sendResult" is a NodeJS-only shortcut for handling all possible types of Graphql responses,
    // See "Advanced Usage" below for more details and customizations available on that layer.
    sendResult(result, res);
  }
});

const port = process.env.PORT || 4000;

app.listen(port, () => {
  console.log(`GraphQL server is running on port ${port}.`);
});

Transports Variations

The processRequest will return one of the following types:

  • RESPONSE: a regular JSON payload
  • MULTIPART_RESPONSE: a multipart response (when @stream or @defer directives are used)
  • PUSH: a stream of events to push back down the client for a GraphQL subscription

If you GraphQL schema doesn't have the type Subscription defined, or the @stream / @defer / @live directives available, you'll get RESPONSE in your result payload, so you can just use sendResult helper to send the response data in one line of code.

If you wish to have more control over you transports, you can use one of the following exported helpers:

  • sendResponseResult - matches the RESPONSE type.
  • sendMultipartResponseResult - matches the MULTIPART_RESPONSE type.
  • sendPushResult - matches the PUSH type.

And you'll be able to construct a custom flow. Here's a quick example for customizing the response per each type of result:

if (result.type === "RESPONSE") {
  sendResponseResult(result, res);
} else if (result.type === "MULTIPART_RESPONSE") {
  sendMultipartResponseResult(result, res);
} else if (result.type === "PUSH") {
  sendPushResult(result, res);
}

This way you can also disable specific responses if you wish, by return an error instead of calling the helpers.

Checkout docs to learn more.

graphql-helix's People

Contributors

0xflotus avatar ardatan avatar chentsulin avatar dan-lee avatar danielrearden avatar dependabot[bot] avatar dotansimha avatar garronej avatar gers2017 avatar github-actions[bot] avatar kamilkisiela avatar karihe avatar maraisr avatar n1ru4l avatar pabloszx avatar paulwib avatar renovate-bot avatar renovate[bot] avatar saihaj avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-helix's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • chore(deps): replace dependency eslint-plugin-node with eslint-plugin-n 14.0.0
  • chore(deps): update dependency @types/express to v4.17.21
  • chore(deps): update dependency esbuild-register to v3.5.0
  • chore(deps): update dependency eslint to v8.57.0
  • chore(deps): update dependency eslint-config-prettier to v8.10.0
  • chore(deps): update dependency eslint-plugin-import to v2.29.1
  • chore(deps): update dependency globby to v12.2.0
  • chore(deps): update dependency lint-staged to v12.5.0
  • chore(deps): update dependency open-cli to v7.2.0
  • chore(deps): update dependency patch-package to v6.5.1
  • chore(deps): update dependency prettier to v2.8.8
  • chore(deps): update dependency ts-jest to v27.1.5
  • chore(deps): update dependency ts-node to v10.9.2
  • chore(deps): update dependency typescript to v4.9.5
  • chore(deps): update graphql-tools (@graphql-tools/schema, @graphql-tools/url-loader, @graphql-tools/utils)
  • chore(deps): update jest monorepo (@types/jest, jest)
  • chore(deps): update typescript-eslint monorepo to v5.62.0 (@typescript-eslint/eslint-plugin, @typescript-eslint/parser)
  • chore(deps): update dependency @next/bundle-analyzer to v14
  • chore(deps): update dependency @types/concurrently to v7
  • chore(deps): update dependency @types/node to v20
  • chore(deps): update dependency concurrently to v8
  • chore(deps): update dependency cpy-cli to v5
  • chore(deps): update dependency del-cli to v5
  • chore(deps): update dependency denoify to v1
  • chore(deps): update dependency eslint-config-prettier to v9
  • chore(deps): update dependency eslint-config-standard to v17
  • chore(deps): update dependency eslint-plugin-promise to v6
  • chore(deps): update dependency get-port to v7
  • chore(deps): update dependency glob to v10
  • chore(deps): update dependency globby to v14
  • chore(deps): update dependency husky to v9
  • chore(deps): update dependency lint-staged to v15
  • chore(deps): update dependency next-remote-watch to v2
  • chore(deps): update dependency open-cli to v8
  • chore(deps): update dependency patch-package to v8
  • chore(deps): update dependency prettier to v3
  • chore(deps): update dependency puppeteer to v22
  • chore(deps): update dependency typescript to v5
  • chore(deps): update dependency wait-on to v7
  • chore(deps): update graphql-tools (major) (@graphql-tools/schema, @graphql-tools/url-loader, @graphql-tools/utils)
  • chore(deps): update jest monorepo to v29 (major) (@types/jest, jest, ts-jest)
  • chore(deps): update react monorepo to v18 (major) (@types/react, @types/react-dom, react, react-dom)
  • chore(deps): update typescript-eslint monorepo to v7 (major) (@typescript-eslint/eslint-plugin, @typescript-eslint/parser)
  • fix(deps): update chakra-ui monorepo (@chakra-ui/icons, @chakra-ui/react, @chakra-ui/theme-tools, @chakra-ui/utils)
  • fix(deps): update dependency copy-to-clipboard to v3.3.3
  • fix(deps): update dependency @guild-docs/client to v1.4.0
  • fix(deps): update dependency @guild-docs/server to v2.2.0
  • fix(deps): update dependency @n1ru4l/graphql-live-query to v0.10.0
  • fix(deps): update dependency @n1ru4l/in-memory-live-query-store to v0.10.0
  • fix(deps): update dependency express-session to v1.18.0 (express-session, @types/express-session)
  • fix(deps): update dependency framer-motion to v5.6.0
  • fix(deps): update dependency graphiql to v1.11.5
  • fix(deps): update dependency graphql-jit to v0.8.6
  • fix(deps): update dependency graphql-language-service-interface to v2.10.2
  • fix(deps): update dependency graphql-ws to v5.16.0
  • fix(deps): update dependency koa to v2.15.2 (koa, @types/koa)
  • fix(deps): update dependency koa-bodyparser to v4.4.1 (koa-bodyparser, @types/koa-bodyparser)
  • fix(deps): update dependency next-i18next to v10.5.0
  • fix(deps): update dependency next-seo to v4.29.0
  • fix(deps): update dependency react-icons to v4.12.0
  • fix(deps): update dependency react-use to v17.5.0
  • fix(deps): update dependency shiki to v0.14.7
  • fix(deps): update emotion monorepo (@emotion/react, @emotion/styled)
  • chore(deps): update actions/cache action to v4
  • chore(deps): update actions/checkout action to v4
  • chore(deps): update actions/setup-node action to v4
  • fix(deps): update dependency @mdx-js/react to v3
  • fix(deps): update dependency framer-motion to v11
  • fix(deps): update dependency graphiql to v3
  • fix(deps): update dependency graphql-upload to v16 (graphql-upload, @types/graphql-upload)
  • fix(deps): update dependency helmet to v7
  • fix(deps): update dependency next-i18next to v15
  • fix(deps): update dependency next-seo to v6
  • fix(deps): update dependency react-icons to v5
  • fix(deps): update dependency shiki to v1
  • fix(deps): update dependency tiny-lru to v11
  • fix(deps): update dependency ws to v8.16.0 (ws, @types/ws)
  • 🔐 Create all rate-limited PRs at once 🔐

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/benchmark.yml
  • actions/checkout v2
  • actions/cache v2
.github/workflows/canary.yml
  • actions/checkout v2
  • actions/setup-node v2
  • bahmutov/npm-install v1
.github/workflows/ci.yml
  • actions/checkout v2
  • actions/setup-node v2
  • actions/cache v2
.github/workflows/release.yml
  • actions/checkout v2
  • actions/setup-node v2
  • actions/cache v2
npm
examples/context/package.json
  • express 4.17.1
  • express-session 1.17.2
  • @types/express 4.17.13
  • @types/express-session 1.17.4
  • ts-node 10.2.1
  • typescript 4.4.4
examples/csp/package.json
  • express 4.17.1
  • helmet 4.6.0
  • @types/express 4.17.13
  • @types/helmet 4.0.0
  • ts-node 10.2.1
  • typescript 4.4.4
examples/error-handling/package.json
  • express 4.17.1
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
examples/express/package.json
  • express 4.17.1
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
examples/fastify/package.json
  • fastify 3.21.6
  • ts-node 10.2.1
  • typescript 4.4.4
examples/file-upload/package.json
  • express 4.17.1
  • graphql-upload ^11.0.0
  • @types/express 4.17.13
  • @types/graphql-upload 8.0.7
  • ts-node 10.2.1
  • typescript 4.4.4
examples/graphql-jit/package.json
  • express 4.17.1
  • graphql-jit 0.5.2
  • tiny-lru 7.0.6
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
examples/graphql-modules/package.json
  • express 4.17.1
  • graphql-modules 1.4.4
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
examples/graphql-ws/package.json
  • express 4.17.1
  • graphql-ws 5.5.0
  • ws 8.2.2
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
  • @types/ws 7.4.7
examples/http/package.json
  • ts-node 10.2.1
  • typescript 4.4.4
examples/koa/package.json
  • koa 2.13.1
  • koa-bodyparser 4.3.0
  • @types/koa 2.13.4
  • @types/koa-bodyparser 4.3.3
  • ts-node 10.2.1
  • typescript 4.4.4
examples/live-queries/package.json
  • @n1ru4l/in-memory-live-query-store 0.7.1
  • @n1ru4l/graphql-live-query 0.8.1
  • express 4.17.1
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
examples/nextjs/package.json
  • next 12.1.0
  • react 17.0.2
  • react-dom 17.0.2
examples/persisted-queries/package.json
  • express 4.17.1
  • @types/express 4.17.13
  • ts-node 10.2.1
  • typescript 4.4.4
package.json
  • @changesets/cli 2.18.1
  • @types/k6 0.35.2
  • @typescript-eslint/eslint-plugin 5.12.1
  • @typescript-eslint/parser 5.12.1
  • eslint 8.4.0
  • eslint-config-prettier 8.3.0
  • eslint-config-standard 16.0.3
  • eslint-plugin-import 2.25.3
  • eslint-plugin-node 11.1.0
  • eslint-plugin-promise 5.2.0
  • husky 7.0.4
  • lint-staged 12.1.2
  • prettier 2.5.1
  • patch-package 6.4.7
  • wait-on 6.0.0
packages/core/package.json
  • @types/chance 1.1.3
  • @types/eventsource 1.1.7
  • @types/jest 27.0.3
  • @graphql-tools/schema 8.3.2
  • chance 1.1.8
  • chalk 5.0.0
  • cpy-cli 3.1.1
  • cross-undici-fetch 0.1.3
  • del-cli 4.0.1
  • denoify 0.10.5
  • eventsource 1.1.0
  • get-port 5.1.1
  • glob 7.2.0
  • globby 12.0.2
  • got 11.8.3
  • husky 7.0.4
  • jest 27.4.3
  • lint-staged 12.1.2
  • move-file-cli 3.0.0
  • puppeteer 12.0.1
  • replacestream 4.0.3
  • ts-jest 27.0.7
  • ts-node 10.4.0
  • typescript 4.5.2
packages/graphiql/package.json
  • @graphql-tools/utils 8.6.2
  • @n1ru4l/graphql-live-query 0.9.0
  • @graphql-tools/url-loader 7.7.2
  • copy-to-clipboard 3.3.1
  • graphiql 1.4.7
  • graphql-language-service-interface 2.9.5
  • react 17.0.2
  • react-dom 17.0.2
  • esbuild 0.14.2
  • @types/react 17.0.37
  • @types/react-dom 17.0.11
  • typescript 4.5.2
website/package.json
  • @chakra-ui/icons 1.1.1
  • @chakra-ui/react 1.7.2
  • @chakra-ui/theme-tools 1.3.1
  • @chakra-ui/utils 1.9.1
  • @emotion/react 11.7.0
  • @emotion/styled 11.6.0
  • @guild-docs/client 1.3.2
  • @guild-docs/server 2.1.4
  • @mdx-js/react 1.6.22
  • @theguild/components 1.8.2
  • framer-motion 5.3.3
  • next 12.1.0
  • next-i18next 10.4.0
  • next-seo 4.28.1
  • react 17.0.2
  • react-dom 17.0.2
  • react-icons 4.3.1
  • react-use 17.3.1
  • remark-admonitions 1.2.1
  • shiki 0.9.14
  • @next/bundle-analyzer 12.0.4
  • @types/concurrently 6.4.0
  • @types/mdx-js__react 1.5.5
  • @types/node 16.11.11
  • @types/react 17.0.37
  • @types/react-dom 17.0.11
  • concurrently 6.4.0
  • cross-env 7.0.3
  • esbuild 0.14.2
  • esbuild-register 3.2.0
  • next-remote-watch 1.0.0
  • open-cli 7.0.1
  • typescript 4.5.2
  • wait-on 6.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

Usage in ESM project broken in v1.8.0

Starting with version 1.8.0 using this package in an ESM project is broken. The most likely commit which broke it is #47.

Steps to reproduce

package.json:

{
  "name": "graphql-helix-bugs",
  "type": "module",
  "dependencies": {
    "graphql": "15.6.0",
    "graphql-helix": "1.8.0"
  }
}

index.js:

import { processRequest } from 'graphql-helix';

console.log(processRequest);

Actual result

$ node index.js 
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in /tmp/graphql-helix-bugs/node_modules/graphql-helix/package.json imported from /tmp/graphql-helix-bugs/index.js
    at throwExportsNotFound (internal/modules/esm/resolve.js:290:9)
    at packageExportsResolve (internal/modules/esm/resolve.js:479:7)
    at packageResolve (internal/modules/esm/resolve.js:644:14)
    at moduleResolve (internal/modules/esm/resolve.js:696:18)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
    at Loader.resolve (internal/modules/esm/loader.js:86:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:56:40)
    at link (internal/modules/esm/module_job.js:55:36) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Expected result

Downgrading to v1.7.0:

$ node index.js 
[AsyncFunction: processRequest]

GraphiQL is posting on another URL.

A have an API route on domain/api/graphql but when i check the network request on GraphiQL playground its accessing domain/graphql. There's no problem when making queries on the client, just when i active GraphiQL playground. Is there a way to change the graphql url path on the server.

Future for this repository

Is there a future planned for this repo?

I think this library it's amazing because of its modularity and some projects at my workplace are considering using graphql-helix on top of Fastify. Even though what already exists is great, I noticed there's been no commits or work done for a little over a month so I'm considering the long term journey of this repo for the decision.

Is there a roadmap, future plans, maintenance plans for this repo or is it meant to be archived soon?

Thanks

Project Website

To make it easier for beginners to get started, a website with documentation for the project would be pretty useful.
I'd like to help make that possible, is somebody already working on that?

graphql-ws example: Invalid query returns "Subscriptions should be sent over WebSocket"

Since v1.11.0, for some reason invalid queries (for example querying a field on an object that does not exist, or forgetting to add a required argument) always result in a PUSH responses from processRequest if the client has text/event-stream in the accepts header.

But if the server does not actually implement PUSH responses, for example when using websockets for subscriptions and implementing that as in the graphql-ws example:

} else {
res.status(422);
res.json({
errors: [new GraphQLError("Subscriptions should be sent over WebSocket.")],
});
}

Then instead of getting a GraphQL validation error you get that "Subscriptions should be sent over WebSocket" message. Notably the version of GraphiQL supplied by graphql-helix has text/event-stream in the accepts header.

My server roughly follows the graphql-ws example, but with some differences like using fastify and having the ws handler in a separate POST-only route, but the issue stems from processRequest returning a PUSH response for GraphQL validation errors when my server does not even implement those otherwise.

To reproduce:

Start the graphql-ws example and execute the (invalid) query in GraphiQL:

mutation { echo(someArgThatDoesNotExist: 1) }

The response will be:

{
  "errors": [
    {
      "message": "Subscriptions should be sent over WebSocket."
    }
  ]
}

Whereas executing the query from a client that only accepts application/json the response will be:

{
    "errors": [
        {
            "locations": [
                {
                    "column": 17,
                    "line": 1
                }
            ],
            "message": "Unknown argument \"someArgThatDoesNotExist\" on field \"Mutation.echo\"."
        }
    ]
}

Is it a bug that GraphQL validation errors are sent by graphql-helix as PUSH responses in the first place?

Or alternatively, is there some option missing in the graphql-ws example to stop the server from sending a PUSH response?

If I implement sendPushResult then obviously the error is correctly returned, but then a client can also query subscriptions over HTTP, which is not my intention.

Avoid bundling GraphiQL

Hey!
Would it be possible to avoid bundling GraphiQL directly, but simply have it as an (optional?) peer dependency?
I am currently having some issue using defer with the latest version of GraphQL Helix and GraphQL 16 (experimental). I suspect this is due to helix using an outdated version of GraphiQL?
Why does it have to be a direct dependency? Could renderGraphiQL not simply take an argument, or make use of a peer dependency?

Error: Cannot find module `<path>\node_module\graphql-helix\dist\dist.js`

I'm currently using GraphQL-Helix in a developmental full stack project. After returning from the Front End to do some API work, my unchanged API is providing this error:

Error: Cannot find module '<path>\node_modules\graphql-helix\dist\dist.js'
    at createEsmNotFoundErr (internal/modules/cjs/loader.js:912:15)
    at finalizeEsmResolution (internal/modules/cjs/loader.js:905:15)
    at resolveExports (internal/modules/cjs/loader.js:437:14)
    at Function.Module._findPath (internal/modules/cjs/loader.js:477:31)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:872:27)
    at Function.Module._load (internal/modules/cjs/loader.js:730:27)
    at Module.require (internal/modules/cjs/loader.js:957:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.graphql-helix/dist (<path>\dist\apps\api\webpack:\<namespace>\external commonjs "graphql-helix\dist":1:1)
    at __webpack_require__ (<path>\dist\apps\api\webpack:\<namespace>\webpack\bootstrap:19:1)

On manual inspection, the dist.js file is not present.

I needed to go back 2 months to find a version of my software where the API compiled correctly. I tried reverting GraphQL-Helix to that version (1.7), however that did not fix the problem in the current build. I believe that this transition is related to my tooling (Nx) transitioning from Webpack 4 to Webpack 5.

My project is an Express app that is part of a larger Nx monorepo. I'm currently using GraphQL-Helix version 1.11, Nx version 13.4. The @nrwl/node plugin is using Webpack version 5.58.1

If anyone has any idea what could be going on, thoughts would be appreciated.

missing exports `send ... ResponseResult` from `node-http` in the barrel file

In @canary v 1.12.0-canary-5297b6b.0

This example does not work anymore as the methods send ... ResponseResult are not exposed anymore.

https://github.com/contra/graphql-helix/blob/master/examples/graphql-ws/server.ts

In theory should be included by:

export * from "./send-result/node-http";

Is it recommended a different approach now? Apart from import {send ... ResponseResut} from 'graphql-helix/node/send-result'

Usage with fastify middleware

Is there a way to make sendResult work with fastify middleware?

for example:

const app = fastify()
app.register(fastifyCors)
app.route({
  method: ['GET', 'POST'],
  url: '/graphql',
  hanlder: async (req,res) => {
    //...
    const result = processRequest({...})
    sendRequest(request, res.raw)
  }
})

In the code above, I want fastifyCors to add access-control-allow-origin header to the response, but it won't.
I'm not completely sure but is that because sendResult is handling "RawResponse" rather than "Response" which is extended by fastify?

500 Error handling suggestions

The way 500 errors are currently handled is that exceptions are caught and GraphQL sends the literal error message back to the consumer in the error response, such as "message": "foo is not a function" and the error trace is swallowed. Instead, it would be preferable if it were a generic message, such as Internal Server Error, and the error were logged.

https://github.com/contrawork/graphql-helix/blob/12895db29a081afc710ab6532937e7129bd0bd8b/packages/core/lib/process-request.ts#L246-L248

where can I find information about graphql-helix's usage of stream.write?

I hope it is not rude to ask this question. Where can one find details about the PassThrough "write" syntax used here https://github.com/contrawork/graphql-helix/blob/master/examples/koa/server.ts/koa/server.ts#L64

const stream = new PassThrough();

stream.write(`data: ${JSON.stringify(result)}\n\n`);
stream.write("---");
stream.write(data.join("\r\n"));
stream.write("\r\n-----\r\n");

I cannot find documentation for calls like these. If I make changes to these calls, for example, if I remove one of the trailing "\n" characters or if I remove the "data: " part at the beginning, the data aren't sent to the client. Where can I find information about this?

Would you recommend a way to stream error(s) to the client? For example, if a permission or data error occurred mid-stream?

Thank you for your any response.

Best approach to set headers through helix resolver in SvelteKit

Hello,
I want to know what is the best approach to set (or return) headers (cookies to be exact) through helix resolver in SvelteKit?
After use login I need to set the jwt and session information in cookies. You can see the Mutation code in here:
https://github.com/MirrorBytes/phorm-kit-vercel/blob/283b0c4036db13b799f22cd2b85a3f89ffab6e1b/src/resolvers/user.ts#L53
And the jwt is generated here:
https://github.com/MirrorBytes/phorm-kit-vercel/blob/283b0c4036db13b799f22cd2b85a3f89ffab6e1b/src/entities/user.ts#L98
I have noticed that processRequest() function return a result with headers in here:
https://github.com/MirrorBytes/phorm-kit-vercel/blob/283b0c4036db13b799f22cd2b85a3f89ffab6e1b/src/routes/graphql.ts#L37

Is there is a way to access the header when processing a request (or resolving a schema) ?
Or can you suggest me a good a approach to do this ?

Thank you.

Adding a resolver hook (for timing)

Hi!

I'm trying to expand our logging to include resolver timings, similar to https://github.com/apollographql/apollo-tracing

Right now we have to wrap each resolver in a higher-order function that tracks and logs execution time. Is it possible to do this globally similar to the contextFactory function?

If not, is this something you would be interested in including if I make a pull request?

saved headers/variables not loading

So values are getting saved in local storage but for some reason when I refresh these values are not getting loaded in the tab editors. I tried on GraphiQL repo's latest build and this feature is working as expected so it is something we need to fix for Helix's GraphiQL instance.
image

lambda example

Lamdba is more popular everyday

it would be good to have an example of aws lamdbda usand graphql-helix

GraphiQL does not work with helmet on

Hi. I was trying to get GraphiQL to work but it got blocked with Content Security Policy errors as seen below:

1

I disabled fastify-helmet and was able to get it working properly. Am I missing some config here?

There are similar issues, but with playground though: graphql/graphql-playground#1283 (comment)

I guess its better to use local js files with GraphiQL rather than files from the CDN (I may be wrong here). Thanks.

Fastify example - cannot set cookies

Hi there,

I am trying out the Fastify example in combination with @fastify/session, basically the equivalent to express-session. This package sets a cookie on the browser whenever I set a session. However, the cookie does not get set through GraphiQL. Most likely because of the res.raw that is being used to send the actual result.

Whenever I create a normal endpoint and set a session, the cookie does get set. So it must be something to do with the /graphql endpoint.

I am not too sure if this is an issue with graphql-helix or with Fastify. I would assume that it has something to do with the sendResult(result, res.raw) which sends the result without any cookies or something.

Not quite sure, hope someone is able to help me out. Thanks!

Edit: I have also created an issue inside the Fastify repository to make sure that this is not an issue related to graphql-helix and could be expected behaviour when using res.raw.

Consider moving GraphiQL functions to a separate package.

While useful, they may not be necessary for all users. It is notable that almost all of the package size of graphql-helix seems to come from dist/render-graphiql.js. While it would be a breaking change, it is relatively straightforward for users to migrate to something like

import {getGraphQLParameters, processRequest} from 'graphql-helix';
import {renderGraphiQL, shouldRenderGraphiQL} from 'graphql-helix-graphiql';

FastifyError: Promise may not be fulfilled with 'undefined' when statusCode is not 204

Hi all,

The server logs show the following message after request:

"level":30,"time":1639778259855,"pid":57154,"hostname":"digiwave","reqId":"req-2","res":{"statusCode":200},"responseTime":1.9270270019769669,"msg":"request completed"}
{"level":30,"time":1639778260754,"pid":57154,"hostname":"digiwave","reqId":"req-3","req":{"method":"POST","url":"/graphql","hostname":"localhost:8080","remoteAddress":"127.0.0.1","remotePort":45644},"msg":"incoming request"}
{"level":50,"time":1639778260755,"pid":57154,"hostname":"digiwave","reqId":"req-3","err":{"type":"FastifyError","message":"Promise may not be fulfilled with 'undefined' when statusCode is not 204","stack":"FastifyError: Promise may not be fulfilled with 'undefined' when statusCode is not 204\n    at /home/developer/node/gql-gateway-svc/node_modules/fastify/lib/wrapThenable.js:30:30\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)","name":"FastifyError","code":"FST_ERR_PROMISE_NOT_FULFILLED","statusCode":500},"msg":"Promise may not be fulfilled with 'undefined' when statusCode is not 204"}

The code can be downloaded here:
gql-gateway-svc.zip

Run the project with command:

npm run gql:gateway:dev

and open in the browser http://localhost:8080/graphql.

What am I doing wrong?

Thanks

Typescript errors with non-experimental graphql

Hi there!

I'm trying out helix (the ecosystem needed an alternative like this - thanks!), and had some trouble with typings:

node_modules/graphql-helix/dist/types.d.ts:1:24 - error TS2305: Module '"../../graphql"' has no exported member 'ExecutionPatchResult'.

1 import { DocumentNode, ExecutionPatchResult, ExecutionResult, GraphQLSchema, OperationDefinitionNode, ValidationRule } from "graphql";
                         ~~~~~~~~~~~~~~~~~~~~

Found 1 error.

After some digging I realized the ExecutionPatchResult interface is only exported on the experimental-stream-defer tag.

I understand helix supports both directives, so I guess it shouldn't be surprising.

However, I think helix could work better with a default installation of graphql. Would you accept a PR where the ExecutionPatchResult is copied instead of imported? Alternatively just a PR that documents it in the readme?

Again, thanks for a great alternative to apollo!

Do you support Batched queries?

Like multiple graphql query in a single interaction.

Request:

[
  {
    operationName: "AddQuery",
    variables: { x: 1, y: 2 },
    query: "query AddQuery ($x: Int!, $y: Int!) { add(x: $x, y: $y) }",
  },
  {
    operationName: "DoubleQuery",
    variables: { x: 1 },
    query: "query DoubleQuery ($x: Int!) { add(x: $x, y: $x) }",
  },
  {
    operationName: "BadQuery",
    query: "query DoubleQuery ($x: Int!) {---", // Malformed Query
  },
];

Response:

[
  {
    data: { add: 3 },
  },
  {
    data: { add: 2 },
  },
  {
    errors: [{ message: "Bad Request" }],
  },
];

variables for a mutation query are not being passed to resolver

Hi,

Maybe I'm missing something, but it seems to me that Input Type objects are not being passed to the implemeting mutation resolvers.

getGraphQLParameters is parsing the variables correctly (and the input is confirmed as being a valid inpt type for the specific mutation).

When the operation, query and variables are passed to the async method processRequest, the resolver is hit, but the mathod doesnt receive any variables...

Some example code:

...
const { operationName, query, variables } = getGraphQLParameters(request);

    console.log({ operationName, query, variables });

    const result = await processRequest({
      operationName,
      query,
      variables,
      request,
      schema,
    });

    if (result.type === 'RESPONSE') {
      result.headers.forEach(({ name, value }) => res.setHeader(name, value));
      res.status(result.status);
      res.json(result.payload);
    } else {
      res.status(400);
      res.json({ error: 'no support for multipart responsed or streams' });
    }
...
import { makeExecutableSchema } from '@graphql-tools/schema';

import typeDefs from './typedefs.generated';

import resolvers from './resolvers';

export const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});
import {
  Claim,
  Policy,
  Customer,
  ClaimInput,
  ClaimCreateResponse,
  // Resolvers,
} from '../types.generated';
import { Claims, Policies, Customers } from '../stores';
import { claims as claimsResolvers } from './claims';

const resolvers = {
  Query: {
    allClaims: () => Claims.findAll(),
    allPolicies: () => Policies.findAll(),
    allCustomers: () => Customers.findAll(),
  },
  Mutation: {
    createClaim: (claim: ClaimInput): Promise<ClaimCreateResponse> => Claims.create(claim),
  },

  ...claimsResolvers,

  Policy: {
    claims: ({ id }: Policy): Claim[] => Claims.findByPolicyId(id),
    customer: ({ customerId }: Policy): Customer | null => Customers.get(customerId),
  },
  Customer: {
    policies: ({ id }: Customer): Policy[] => Policies.findByCustomerId(id),
  },
};

export default resolvers;
import { find, filter } from 'lodash';
import { Claim, ClaimInput, ClaimCreateResponse } from '../types.generated';
import { validate } from '../validators';

import { claims as claimStubs } from './data';

const Claims = {
  get: (id: string): Claim | null => find(claimStubs, { id }) || null,
  findAll: (): Claim[] => claimStubs,
  findByPolicyId: (_id: string): Claim[] => filter(claimStubs, { policyId: _id }),
  create: async (claim: ClaimInput): Promise<ClaimCreateResponse> => {
    // validate input
    console.log(claim);
    if (validate('ClaimInput')(claim)) {
      return {
        claim: {
          ...claim,
          id: 'generatedId',
        },
      };
    } else {
      throw new Error('not valid');
    }
  },
};

export default Claims;

Persisted query examples appear to be incorrect

Both examples work fine if the query has previously been persisted, but neither appears to handle the case where we are seeing a query for the first time that needs to be persisted for future use....

Helix in Gateway to pass headers down to subschema?

Having some issues with headers not making it down to sub-services in my gateway setup.

Namely we send authorization headers through our client to the gateway and I would like to use graphql-shield on the subschema services to check against these headers.

It doesn't seem like the auth headers are making it past the gateway into the subschema. Is there a way to resolve this? Am I setting up my helix/gateway incorrect? (Almost identical to the example) Any help would be appreciated, thank you!

const fastify = require('fastify')
const { getGraphQLParameters, processRequest, renderGraphiQL, shouldRenderGraphiQL, sendResult } = require("graphql-helix");
const { envelop, useLogger, useSchema, useTiming } = require("@envelop/core")
const { useGenericAuth } = require('@envelop/generic-auth')
const { useResponseCache } = require('@envelop/response-cache');
const { resolveUserFn, validateUserFn } = require('./authUtils')
const SchemaLoader = require('./SchemaWithLoader')
const MIN_IN_MS = 60000

SchemaLoader.reload().then(async () => {
  const app = fastify()

  const getEnveloped = envelop({
    plugins: [
      // useNewRelic also exists, but isnt setup in this use case
      useTiming({
        skipIntrospection: true
      }),
      // useResponseCache({
      //   ttl: 1000,
      // }),
      useSchema(SchemaLoader.schema),

    ]
  })
  app.route({
    method: ['GET', 'POST'],
    url: '/graphql',
    async handler(req, res) {
      const request = {
        body: req.body,
        headers: req.headers,
        method: req.method,
        query: req.query,
      }
      const { parse, validate, contextFactory, execute, schema } = getEnveloped({ req })

      if (shouldRenderGraphiQL(request)) {
        res.type("text/html");
        res.send(renderGraphiQL())
        return
      }
      const { operationName, query, variables } = getGraphQLParameters(request);
      const result = await processRequest({
        operationName,
        query,
        variables,
        request,
        schema,
        parse,
        validate,
        execute,
      })

      sendResult(result, res.raw)

    }
  })
  app.listen(3000, () => {
    console.log(`⛩️  Gateway Server ready at http://localhost:3000/graphql`);
  })
  SchemaLoader.autoRefresh(30000) 
})

[Question] Have you thought about graphql-jit?

I don't know much about graphql engines, but I've read that graphql-jit is faster than graphql-js.

https://github.com/zalando-incubator/graphql-jit

What about some support for graphql-jit?

From graphql-jit documentation:

$ yarn benchmark skip-json
Starting introspection
graphql-js x 1,941 ops/sec ±2.50% (225 runs sampled)
graphql-jit x 6,158 ops/sec ±2.38% (222 runs sampled)
Starting fewResolvers
graphql-js x 26,620 ops/sec ±2.41% (225 runs sampled)
graphql-jit x 339,223 ops/sec ±2.94% (215 runs sampled)
Starting manyResolvers
graphql-js x 16,415 ops/sec ±2.36% (220 runs sampled)
graphql-jit x 178,331 ops/sec ±2.73% (221 runs sampled)
Starting nestedArrays
graphql-js x 127 ops/sec ±1.43% (220 runs sampled)
graphql-jit x 1,316 ops/sec ±2.58% (219 runs sampled)
Done in 141.25s.

Prohibiting SSE via POST breaks `graphql-sse` usage (since 1.11.0)

Since v1.11.0 text/event-stream / SSE requests are forbidden via POST:

POST requests that try to execute Subscription operations will now receive an error and 405 status code. This is not considered a breaking change as SSE is not doable over POST by the specification and was never officially supported.

In helix-flare tests we are using graphql-sse to create a subscription connection. It seems, that graphql-sse uses POST and body to instantiate a connection which is now refused by graphql-helix.

Tests are running fine for helix-flare with [email protected] but are failing for 1.11.0.

Am I wrong here by assuming that this should actually work fine with POST requests, or should I choose a different approach?

proposal: merge PUSH and MULTIPART_RESPONSE into STREAM

Subscriptions and defer/stream and live queries can be done via both SSE and Multipart. It makes sense to push the responsibility of choosing what the user expects to user-land (or the response helpers), instead of handling this within processRequest


Related #160

Export parseQuery function

I think for custom use-cases it makes sense to expose the parseQuery function to the user in order to have a consistent error handling.

Compatability with other libraries on deno

Hey! I saw that helix has some support for running on deno, and was inspired to make one of my projects (GiraphQL) work on deno as well.

GiraphQL is a graphql schema builder that doesn't handle any of the server logic, making it an ideal pairing with helix. I couldn't find much info on how you recommend setting up helix on deno, but after some tinkering got something that works.

A couple issues I ran into:

  • It looks like something in render-graphiql.ts causes deno's parser to choke and crash the process. I worked around this by just importing the other files directly and using something else to load graphql playground.
  • the way the graphql library is written makes it impossible to use multiple copies of graphql together. This isn't a huge issue in node because you can use things like yarn resolutions to ensure only one version of graphql exists in you node_modules directory. This can be worked around in deno using import-maps. The down side of this is that every time you update helix you need to dig through the source to find exactly which version (and url) is being used to load graphql, and then update your import-map to match.

I was curious if you had run into any of these issues or had thoughts on best practices to recomend to users of our libraries.

One idea I had that may make sense is to always use the same url/version of graphql (something like https://cdn.skypack.dev/graphql?dts), and strongly encourage users to use import maps that replace this version. By using a non-versioned url, new users will pick up the latest version by default, and user upgrading libraries don't need to worry as much about check the specific versions being imported by the new version.

I put together some docs with an working example of GiraphQL and helix here https://giraphql.com/guide/deno if you're curious.

Slow processRequest when error thrown

Did some basic benchmarking and noticed a normal "Hello world" request finishes in <1ms but if the resolver throws an error it takes > 100ms

Any idea what could be slowing things down?

Need help setting up Denoify

Hey @danielrearden,
Nice project,

I saw you attempted to use package.json -> denoify -> ports with a pika URL.
Unfortunately only deno.land/x and githubraw url are supported by this method.

Here what you need is to write a custom replacer that will tell Denoify how to replace the graphQL imports.

Do you need help with that?

Regards,

Events not being streamed to client over SSE

I'm trying to implement GraphQL subscriptions over SSE, everything works fine until I write the data over the stream, like the title says, none of the data I write with res.write(data: ...) is streamed to the client, here's proof that none of the events are being streamed:

image

And this is how I'm trying to make this work, I don't see anything wrong with it...

res.writeHead(200, {
  'Content-Type': 'text/event-stream',
  Connection: 'keep-alive',
  'Cache-Control': 'no-cache',
});

// Send a keep-alive message every 15 seconds so the client knows that the
// connection is still "alive" and doesn't disconnect. More info here:
// https://stackoverflow.com/questions/19778231/sse-eventsource-times-out-after-1-hour-22-minutes-is-there-any-way-to-keep-it/20060461#20060461
let counter = 0;
const timer = setInterval(() => {
  counter++;
  res.write(`data: ${counter}\n\n`);
}, 15000);

req.on('close', () => {
  clearInterval(timer);
  result.unsubscribe();
});

await result.subscribe(result => {
  console.log(JSON.stringify(formatResult(result)));

  res.write(`data: ${JSON.stringify(formatResult(result))}\n\n`);
});

Update to support GraphQL JS v16

Hi,

After upgrading to GraphQL JS v16 (released 28th of Oct 2021) while using this library, you should receive this error.

 Must provide document.

      at devAssert (node_modules/graphql/jsutils/devAssert.js:12:11)
      at assertValidExecutionArguments (node_modules/graphql/execution/execute.js:145:40)
      at Object.execute (node_modules/graphql/execution/execute.js:74:3)

This is discussed in relation to other libraries here: graphql/graphql-js#3245 (comment)

The most critical change required would be to pass an object rather than params to execute().

Mark.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.