question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Usage with fastify middleware

See original GitHub issue

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?

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

13reactions
dotansimhacommented, Nov 23, 2021

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 || ''))
}
5reactions
n1ru4lcommented, Nov 23, 2021

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? https://github.com/contra/graphql-helix/issues/161

Read more comments on GitHub >

github_iconTop Results From Across the Web

Middleware - Fastify
Fastify offers some alternatives to the most commonly used middleware, such as @fastify/helmet in case of helmet , @fastify/cors for cors , and...
Read more >
fastify/middie: Middleware engine for Fastify. - GitHub
@fastify/middie is the plugin that adds middleware support on steroids to Fastify. The syntax style is the same as express/connect. Does not support...
Read more >
Fastify middleware - access to query and params?
Fastify is asking developers to think more in terms of Hooks and less in terms of Middleware. This provides more flexibility and greater...
Read more >
Middlewares - Fastify
Fastify out of the box provides an asynchronous middleware engine compatible with Express and Restify middlewares. If you need a visual feedback to...
Read more >
How to Migrate Your App from Express to Fastify - SitePoint
The fastify-express plugin adds full Express compatibility to Fastify. It provides a use() method which we can use to add Express middleware ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found