Giter Site home page Giter Site logo

Usage with fastify middleware about graphql-helix HOT 6 OPEN

contra avatar contra commented on June 11, 2024 1
Usage with fastify middleware

from graphql-helix.

Comments (6)

dotansimha avatar dotansimha commented on June 11, 2024 14

Just to mention that we had a similar issue today, with fastify-cors. The headers set by this plugin was added to the fastify.reply and not to fastify.reply.raw, and sendNodeResult was ignoring those.

Doing that before calling sendNodeResult should work as a temporary workaround:

for (const [key, value] of Object.entries(res.getHeaders())) {
  res.raw.setHeader(key, String(value || ''))
}

from graphql-helix.

n1ru4l avatar n1ru4l commented on June 11, 2024 5

I checked out how different plugins handle sending responses with res.raw.write:

https://github.com/NodeFactoryIo/fastify-sse-v2/blob/b1686a979fbf655fb9936c0560294a0c094734d4/src/plugin.ts#L6-L21

export const plugin: FastifyPluginAsync<SsePluginOptions> = async function(
  instance,
  options
): Promise<void> {
  instance.decorateReply("sse", function(
    this: FastifyReply,
    source: AsyncIterable<EventMessage>
  ): void {
    Object.entries(this.getHeaders()).forEach(([key, value]) => {
      this.raw.setHeader(key, value);
    });
    this.raw.setHeader("Content-Type", "text/event-stream");
    this.raw.setHeader("Connection", "keep-alive");
    this.raw.setHeader("Cache-Control", "no-cache,no-transform");
    this.raw.setHeader("x-no-compression", 1);
    this.raw.write(serializeSSEEvent({ retry: options.retryDelay || 3000 }));
    toStream(transformAsyncIterable(source)).pipe(this.raw);
  });
};

The solution you proposed @dotansimha seems to be the correct one!


We should probably only give users the headers and the payload. The responsibility for glueing that to the framework of their choice should be more or less straightforward and can be guided with tutorials, recipes and example applications.

One of the main reasons why we added these helpers was boilerplate code we had to write over and over again for the multi-part protocol. Now we realize that we tried to own too much...

Instead of owning the whole pipeline we could only have helper functions for formatting the payload into the desired protocol and yield the string partials to the user:

async function* multiPartProtocol(
  source: AsyncIterable<ExecutionResult>,
  transformResult: TransformResultFn
) {
  yield "---";
  for await (const result of source) {
    const chunk = Buffer.from(JSON.stringify(transformResult(result)), "utf8");
    const data = [
      "",
      "Content-Type: application/json; charset=utf-8",
      "Content-Length: " + String(chunk.length),
      "",
      chunk
    ];

    if (result.hasNext) {
      data.push("---");
    }
    yield "\r\n";
  }
  yield "\r\n-----\r\n";
}
async function* sseProtocol(
  source: AsyncIterable<ExecutionResult>,
  transformResult: TransformResultFn
) {
  for await (const result of source) {
    yield `data: ${JSON.stringify(transformResult(result))}\n\n`;
  }
}

Furthermore, I spotted that Fastify can handle stream payloads 🤔 https://github.com/fastify/fastify/blob/0439d5ffcab1709659353e8633a9a1ff36595c22/lib/reply.js#L426-L431

SO maybe the result returned from processRequest, could implement that interface (if it is a stream response and not only a single payload). Then for fastify we actually might not need to use res.raw at all!

From a user-land perspective the code would look like this:

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,
    };

    if (shouldRenderGraphiQL(request)) {
      res.type("text/html");
      res.send(renderGraphiQL({}));
    } else {
      const request = {
        body: req.body,
        headers: req.headers,
        method: req.method,
        query: req.query,
      };
      const { operationName, query, variables } = getGraphQLParameters(request);
      const { headers, source } = await processRequest({
        operationName,
        query,
        variables,
        request,
        schema,
      });

      // headers determined by helix
      for (const header of headers) {
        res.setHeader(header.key, header.value)
      }
      // source is a stream or string
      res.send(source)
    }
  },
});

There is still an open question for me: Should we even tell people what headers/protocols they should use? #161

from graphql-helix.

ricardo-valero avatar ricardo-valero commented on June 11, 2024 2

I'm a bit biased because it's what I'm using right now, but check out graphql-ez it uses graphql-helix and has several integrations, not only for fastify, but express, koa and more.

Here's the fastify integration

Hope it helps in some way!

from graphql-helix.

taneba avatar taneba commented on June 11, 2024 1

@dotansimha
Thank you for elaborating that and it makes sense to keep it framework agnostic.

How about taking an approach like what graphql-ws is doing, which is to have a helper function for certain library.

Their example for fastify:

import Fastify from 'fastify'; // yarn add fastify
import fastifyWebsocket from 'fastify-websocket'; // yarn add fastify-websocket
import { makeHandler } from 'graphql-ws/lib/use/fastify-websocket';
import { schema } from './previous-step';

const fastify = Fastify();
fastify.register(fastifyWebsocket);

fastify.get('/graphql', { websocket: true }, makeHandler({ schema }));

fastify.listen(4000, (err) => {
  if (err) {
    fastify.log.error(err);
    return process.exit(1);
  }
  console.log('Listening to port 4000');
});

from graphql-helix.

dotansimha avatar dotansimha commented on June 11, 2024

Hi @taneba ,
Thanks for reaching out. Currently, sendResult is using the raw request because we wanted it to be agnostic to the HTTP server.
I think in cases where you want to use different setup, you might need to use the code from sendResult and adjust to to your needs (and use fastify and not the raw HTTP).

I'm not sure how to address this, because it seems specific for fastify?

@n1ru4l thoughts?

from graphql-helix.

jetaggart avatar jetaggart commented on June 11, 2024

Any further thoughts on this? This was incredible confusing to figure out. I'm using fastify-cors and this took hours to figure out that the helix setup was swallowing the headers. @dotansimha answer worked for me!

from graphql-helix.

Related Issues (20)

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.