Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for fastify 5 #296

Open
notaphplover opened this issue Sep 17, 2024 · 15 comments · May be fixed by #297 or #299
Open

Add support for fastify 5 #296

notaphplover opened this issue Sep 17, 2024 · 15 comments · May be fixed by #297 or #299

Comments

@notaphplover
Copy link
Contributor

fastify@5 is released 😃.

@as-integrations/fastify peerDependencies include fastify@^4.4.0. Please consider releasing a new version supporting fastify@5.

@olyop
Copy link
Collaborator

olyop commented Sep 17, 2024

Thanks for the heads up! Should be able to start on this soon.

@zckrs
Copy link

zckrs commented Oct 4, 2024

Any news about this ?

zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
@zckrs zckrs linked a pull request Oct 4, 2024 that will close this issue
zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
zckrs added a commit to zckrs/apollo-server-integration-fastify that referenced this issue Oct 4, 2024
@ms-saurabh ms-saurabh linked a pull request Oct 24, 2024 that will close this issue
@cupofjoakim
Copy link

I'm joining the crowd wishing for a swift resolution to this. Considering there's two open pr's trying to solve the same thing I hope the maintainers push for one of them soon.

@notaphplover
Copy link
Contributor Author

I see, should we fork the repo, merge the PR and publish it under another org? It's been a while since I opened the issue

@cupofjoakim
Copy link

@olyop @trevor-scheer Could you please take a gander at the two prs?

@niraj-khatiwada
Copy link

Any update on this? I'm stuck on my Fastify/Nest server updates as well due to this.

@matheuspleal
Copy link

Hello! Any update on support for Fastify version 5?

@hadeed85
Copy link

any news on this?

@yurtaev
Copy link

yurtaev commented Dec 11, 2024

Workaround. Use yarn patch or something similar:

package.json:

"@as-integrations/fastify": "patch:@as-integrations/fastify@npm%3A2.1.1#~/.yarn/patches/@as-integrations-fastify-npm-2.1.1-6c130ea02e.patch",

.yarn/patches/@as-integrations-fastify-npm-2.1.1-6c130ea02e.patch:

diff --git a/build/cjs/plugin.js b/build/cjs/plugin.js
index 9c6d1536b0a72a0bfee7b1cf0481eeb3f726ccf8..a48242f19512288919dbdbc496ec18f3698ea2ca 100644
--- a/build/cjs/plugin.js
+++ b/build/cjs/plugin.js
@@ -5,7 +5,7 @@ const fastify_plugin_1 = require("fastify-plugin");
 const handler_js_1 = require("./handler.js");
 const utils_js_1 = require("./utils.js");
 const pluginMetadata = {
-    fastify: "^4.4.0",
+    fastify: "^5.1.0",
     name: "@as-integrations/fastify",
 };
 function fastifyApollo(apollo) {
diff --git a/build/esm/plugin.js b/build/esm/plugin.js
index 6bfb4b052e2fd83e16ce642e800b46b201b8a53a..efd5c85ed40d7ec08ab3326865597e30f372e922 100644
--- a/build/esm/plugin.js
+++ b/build/esm/plugin.js
@@ -2,7 +2,7 @@ import { fastifyPlugin } from "fastify-plugin";
 import { fastifyApolloHandler } from "./handler.js";
 import { isApolloServerLike } from "./utils.js";
 const pluginMetadata = {
-    fastify: "^4.4.0",
+    fastify: "^5.1.0",
     name: "@as-integrations/fastify",
 };
 export function fastifyApollo(apollo) {

@cupofjoakim
Copy link

This integration seems unmaintained and dead. Forking it is likely the solution, but for me this means I'll stay with the official express integration instead.

@alizahid
Copy link

Until an update is released, here's something I cooked up that might be of use;

import { Readable } from 'node:stream'

import {
  type ApolloServer,
  HeaderMap,
  type HTTPGraphQLRequest,
  type HTTPGraphQLResponse,
} from '@apollo/server'
import { type FastifyReply, type FastifyRequest } from 'fastify'
import plugin from 'fastify-plugin'

import { type Context, createContext } from './server/context'

export function fastifyApollo(apollo: ApolloServer<Context>) {
  return plugin(
    (fastify) => {
      fastify.route({
        async handler(request, reply) {
          const response = await apollo.executeHTTPGraphQLRequest({
            context: () => createContext(request, reply),
            httpGraphQLRequest: createRequest(request),
          })

          return handleResponse(reply, response)
        },
        method: ['get', 'post', 'options'],
        url: '/graphql',
      })
    },
    {
      fastify: '5.x',
      name: 'apollo',
    },
  )
}

function createRequest(request: FastifyRequest): HTTPGraphQLRequest {
  const url = new URL(request.url, `${request.protocol}://${request.hostname}`)

  const headers = new HeaderMap()

  for (const [key, value] of Object.entries(request.headers)) {
    if (value) {
      headers.set(key, Array.isArray(value) ? value.join(', ') : value)
    }
  }

  return {
    body: request.body,
    headers,
    method: request.method.toUpperCase(),
    search: url.search,
  }
}

function handleResponse(
  reply: FastifyReply,
  { body, headers, status }: HTTPGraphQLResponse,
) {
  for (const [key, value] of headers) {
    void reply.header(key, value)
  }

  void reply.code(status ?? 200)

  if (body.kind === 'complete') {
    return body.string
  }

  const readable = Readable.from(body.asyncIterator)

  return reply.send(readable)
}

Usage;

await fastify.register(fastifyApollo(apollo))

Most of the code is copied from this package and refactored a bit.

Does anybody know if reply.header and reply.code needs to be awaited?

@trevor-scheer
Copy link
Member

Hey y'all. Sorry for the silence here, unfortunately priorities have taken my attention elsewhere.

  • @olyop would you be open to adding other contributors to this project? I see 2 open PRs for this. We should get them reviewed and released.
  • Unless v5 is backward-compatible in the ways we depend on it, we should be bumping the fastify peer deps to ^5 and bumping the major version of this package to v3. Ideally we open a new version-2 branch in order to backport fixes to if necessary.
  • We should drop support for EOL node versions and generally update other dependencies if possible.

I can't commit to helping with this work but I would like to enable you folks to do it.

@thekevinbrown
Copy link

Hey @trevor-scheer, sorry to prod, but just wanted to check in. Is there anything we can do to help here?

@lod911
Copy link

lod911 commented Jan 31, 2025

It looks like @olyop is no longer active: https://github.com/olyop?tab=overview&from=2024-12-01&to=2024-12-31.
His last activity was in August of last year.

Is anyone available to at least review and accept pull requests? That would be great. Alternatively, bringing in new contributors could help keep this official plugin—mentioned on https://www.apollographql.com/docs/apollo-server—alive.

Thanks for taking action!

@vslipchenko
Copy link

vslipchenko commented Feb 2, 2025

Here is the updated version of the plugin (including fastifyApolloDrainPlugin), which is based on the original one, but less overloaded with generic parameters.
I've dropped unnecessary generic parameters since they were using defaults anyway and were of little significance.

P.S. Just as the original repo it requires fastify-plugin to be installed.

// --- Types

interface FastifyApolloPluginContext<Context extends BaseContext> {
  context?: ApolloFastifyContextFunction<Context>;
}

type ApolloFastifyContextFunctionArgument = [request: FastifyRequest, reply: FastifyReply];

type ApolloFastifyContextFunction<Context extends BaseContext> = ContextFunction<
  ApolloFastifyContextFunctionArgument,
  Context
>;

type FastifyApolloPluginOptions<Context extends BaseContext> = FastifyRegisterOptions<
  FastifyApolloPluginContext<Context>
>;

// --- Plugins

// Inspired by outdated fastify apollo plugin:
// https://github.com/apollo-server-integrations/apollo-server-integration-fastify/blob/main/src/plugin.ts
const fastifyApollo = <Context extends BaseContext = BaseContext>(
  apollo: ApolloServer<Context>,
): FastifyPluginAsync => {
  const apolloServerProvided = (value: unknown): boolean => value instanceof ApolloServer;

  if (!apolloServerProvided(apollo)) throw new Error('Apollo server instance is not provided');

  apollo.assertStarted('fastifyApollo()');
  
  // See: https://github.com/fastify/fastify-plugin?tab=readme-ov-file#metadata
  const pluginMetadata: PluginMetadata = {
    fastify: '^5.2', // This may be different for you based on your installed version of fastify
    name: 'fastifyApollo',
  };

  const fastifyRequestToGraphQLRequest = (request: FastifyRequest): HTTPGraphQLRequest => {
    const httpHeadersToMap = (headers: IncomingHttpHeaders): HeaderMap => {
      const map = new HeaderMap();

      for (const [key, value] of Object.entries(headers)) {
        if (value) {
          map.set(key, Array.isArray(value) ? value.join(', ') : value);
        }
      }

      return map;
    };

    return {
      body: request.body,
      method: request.method.toUpperCase(),
      headers: httpHeadersToMap(request.headers),
      search: new URL(request.url, `${request.protocol}://${request.hostname}/`).search,
    };
  };

  const fastifyApolloHandler = <Context extends BaseContext>(
    apollo: ApolloServer<Context>,
    options: FastifyApolloPluginContext<Context>,
  ): RouteHandlerMethod => {
    const defaultContext: ApolloFastifyContextFunction<Context> = () => Promise.resolve({} as Context);

    const contextFunction = options?.context ?? defaultContext;

    return async (request: FastifyRequest, reply: FastifyReply): Promise<string> => {
      const httpGraphQLResponse = await apollo.executeHTTPGraphQLRequest({
        httpGraphQLRequest: fastifyRequestToGraphQLRequest(request),
        context: () => contextFunction(request, reply),
      });

      const {headers, body, status} = httpGraphQLResponse;

      for (const [headerKey, headerValue] of headers) {
        void reply.header(headerKey, headerValue);
      }

      void reply.code(status === undefined ? 200 : status);

      if (body.kind === 'complete') {
        return body.string;
      }

      const readable = Readable.from(body.asyncIterator);

      return reply.send(readable);
    };
  };

  return fastifyPlugin(async (fastify: FastifyInstance, options: FastifyApolloPluginContext<Context>): Promise<void> => {
    const path = '/graphql';
    const method = ['GET', 'POST', 'OPTIONS'];

    fastify.route({
      method,
      url: path,
      handler: fastifyApolloHandler<Context>(apollo, options),
    });
  }, pluginMetadata);
};

// Inspired by outdated fastify apollo drain plugin:
// https://github.com/apollo-server-integrations/apollo-server-integration-fastify/blob/main/src/drain-plugin.ts
const fastifyApolloDrainPlugin = (fastify: FastifyInstance): ApolloServerPlugin => {
  return {
    async serverWillStart(): Promise<GraphQLServerListener> {
      return {
        async drainServer(): Promise<void> {
          // `closeAllConnections` was added in v18.2 - @types/node are v16.
          if ('closeAllConnections' in fastify.server) {
            // If fastify.close() takes longer than 10 seconds - run the logic to force close all connections
            const timeout = setTimeout(() => {
              fastify.server.closeAllConnections();
            }, 10_000);

            await fastify.close();
            clearTimeout(timeout);
          }
        },
      };
    },
  };
};

// --- Usage

interface MyContext {
    // Here I use an auth token as a part of the context, you may have a different context
    authToken: string;
}

const extractAuthToken: ApolloFastifyContextFunction<MyContext> = async (request: FastifyRequest) => {
    // ... Your logic goes here
};

const app = fastify();

// Apollo server initialization logic, you may have it different
const server = new ApolloServer<MyContext>({
    schema,
    plugins: [fastifyApolloDrainPlugin(app)],
});

// Spin up the Apollo server
await server.start();

// Register the plugin
await app.register<FastifyApolloPluginOptions<MyContext>>(fastifyApollo(server), {
    context: extractAuthToken
});

// Spin up the fastify server
await app.listen({
    port: 4000 // ... or whatever your port number is
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet