apollo-server-integration-fastify icon indicating copy to clipboard operation
apollo-server-integration-fastify copied to clipboard

Add support for fastify 5

Open notaphplover opened this issue 1 year ago • 20 comments
trafficstars

fastify@5 is released :smiley:.

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

notaphplover avatar Sep 17 '24 16:09 notaphplover

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

olyop avatar Sep 17 '24 23:09 olyop

Any news about this ?

zckrs avatar Oct 04 '24 14:10 zckrs

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.

cupofjoakim avatar Nov 20 '24 16:11 cupofjoakim

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

notaphplover avatar Nov 21 '24 09:11 notaphplover

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

cupofjoakim avatar Nov 21 '24 09:11 cupofjoakim

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

niraj-khatiwada avatar Nov 30 '24 15:11 niraj-khatiwada

Hello! Any update on support for Fastify version 5?

matheuspleal avatar Dec 08 '24 13:12 matheuspleal

any news on this?

hadeed85 avatar Dec 10 '24 21:12 hadeed85

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) {

yurtaev avatar Dec 11 '24 03:12 yurtaev

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.

cupofjoakim avatar Dec 11 '24 09:12 cupofjoakim

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?

alizahid avatar Dec 17 '24 13:12 alizahid

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.

trevor-scheer avatar Dec 17 '24 21:12 trevor-scheer

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

thekevinbrown avatar Jan 22 '25 23:01 thekevinbrown

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!

lod911 avatar Jan 31 '25 09:01 lod911

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

vslipchenko avatar Feb 02 '25 14:02 vslipchenko

Thanks @vslipchenko, your code works well with some adjustment of the type, just one correction it's critical There is a change in the FastiifyRequest following this PR in Fastify v5

In fastifyRequestToGraphQLRequest it's better to use request.host instead of request.hostname

MatteoGranziera avatar Feb 14 '25 11:02 MatteoGranziera

@trevor-scheer @olyop is there any news please?

matheuspleal avatar Feb 19 '25 11:02 matheuspleal

I'm also curious for updates here - trying to upgrade to the latest version of Nest.js / Fastify plugins and ran into this issue. Thanks to all the maintainers here!

isaacaskew avatar Feb 20 '25 21:02 isaacaskew

Prepared temporary fork @nitra/as-integrations-fastify

vitaliytv avatar Feb 22 '25 09:02 vitaliytv

Tried the temporary fork but the Nest.js GraphQL integration throws an error that @as-integrations/fastify isn't found. I need Fastify 5 for the latest version of Nest.js but can't upgrade my application until @as-integrations/fastify is also upgraded:

 ERROR [PackageLoader] The "@as-integrations/fastify" package is missing. Please, make sure to install it to take advantage of GraphQLModule.

I might hack around to make it work instead, but looking forward to an official version bump on the main package.

isaacaskew avatar Mar 05 '25 18:03 isaacaskew

Hi! Looks like there are three PRs opened to add support for Fastify v5 to this community-maintained package (#297, #299, and #300).

Because Apollo's team does not actively use many different web frameworks, Apollo Server 4 split off the responsibility for maintaining framework-specific integrations to the community. The Fastify integration was originally maintained by @olyop.

If @olyop is no longer interested in maintaining this package (which appears to be the case as these PRs have been open for more than half a year), we'd be happy to add other Fastify users as maintainers of this package on GitHub and npm. We don't possess the Fastify-specific expertise to evaluate these PRs ourselves nor do we have anywhere we could dogfood them. Let us know here if you're interested in maintaining this package!

glasser avatar May 27 '25 19:05 glasser

Hey @glasser I'm happy to contribute to getting this package back on track. I have a lot a open source maintenance experience with large projects

xzyfer avatar May 28 '25 00:05 xzyfer

@xzyfer thanks for raising your hand! Would you be open to a DM or email to talk through a few details and hopefully get you set up as a maintainer?

bignimbus avatar May 28 '25 20:05 bignimbus

@bignimbus sure thing. I go by the same handle on Gmail, twitter, and blue sky.

xzyfer avatar May 28 '25 23:05 xzyfer

For anyone struggling to use one of the forked packages as an interim fix, this is what worked for me. The key was specifying the forked dependency in both the dependencies and overrides sections of package.json.

Without both edits, I was hitting the same error on server startup: The "@as-integrations/fastify" package is missing.

{
  "dependencies": {
    "@as-integrations/fastify": "npm:[email protected]"
  },

  [...]

  "overrides": {
    "@as-integrations/fastify": "npm:[email protected]"
  }
}

Any idea when the main package @as-integrations/fastify may see an update? Thanks!

Tarkan2467 avatar Jun 13 '25 20:06 Tarkan2467

Any news about the PR review/release ?

Maxou44 avatar Jul 05 '25 19:07 Maxou44

Update, I've been on-boarded as a contributor to keep this package up with fastify releases. We're just waiting for my npm publishing permissions to get sorted about by the Apollo team.

xzyfer avatar Jul 06 '25 02:07 xzyfer

The open PR has been moved from my fork into the repo at #302. The currently blocker is getting circle ci access. If all else fails the repo can be converted to github actions for CI.

xzyfer avatar Jul 22 '25 02:07 xzyfer

This should be fixed in v3.0.0. A major version bump was required because the fastify v5 required us to raise the minimum Node version to 20, and Typescript version to 5.4.0

xzyfer avatar Aug 08 '25 07:08 xzyfer

Awesome ! Thanks for the update @xzyfer ! :)

Maxou44 avatar Aug 08 '25 07:08 Maxou44