Upgrade apollo 3 to 4 and change buildFederatedSchema by buildSubgraphSchema

I have upgraded from apollo v3 to v4
I change setting in the apollo gateway from

buildService({ name, url }) {
      return new LocalGraphQLDataSource(
        applyMiddleware(
          buildFederatedSchema([
            {
              typeDefs: AppModule.typeDefs,
              resolvers: AppModule.resolvers,
            },
          ])
        )
      );
  },

to

buildService({ name, url }) {
      return new LocalGraphQLDataSource(
        applyMiddleware(
          buildSubgraphSchema(
            {
              typeDefs: AppModule.typeDefs,
              resolvers: AppModule.resolvers,
            },
          )
        )
      );
  },

AppModule data from

export const AppModule = createApplication({
  modules: [
     AboutModule 
  ],
});

This my module setup

export const AboutModule = createModule({
  id: 'About',
  typeDefs: gql`
  type Query {
    me: User
  }

  type User{
    id: ID!
    username: String
  }
`,
  resolvers: [ {
    Query: {
      me() {
        console.log("Query");
        return { id: '1', username: '@ava' };
      },
    },
  } ],
});

The server is up but when I call a API the resolved isn’t call. Can you pls help me to find it out. Thanks you.

Everything looks fine so it’s hard to debug just by looking at the code. Can you isolate the change to either the Apollo Server upgrade OR the switch to buildSubgraphSchema?

Can you share more details about what isn’t working? Any errors you’re seeing when you send a request?
Can you share your Apollo Server config?
Can you share a reproduction?

1 Like

Thanks for you anwser.
I have do both Apollo Server upgrade AND the switch to buildSubgraphSchema
This is my Apollo server config:

import {
  ApolloGateway, IntrospectAndCompose,
  LocalGraphQLDataSource,
  RemoteGraphQLDataSource
} from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault } from '@apollo/server/plugin/landingPage/default';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { PrismaClient } from '@prisma/client';
import pkg from 'body-parser';
import cors from 'cors';
import express from 'express';
import { GraphQLFormattedError } from 'graphql';
import { applyMiddleware } from 'graphql-middleware';
import { gql } from 'graphql-modules';
import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.js";
import http from 'http';
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import 'module-alias/register';
import "reflect-metadata";
import '../prisma/seed';
import { CONFIG } from './config';
import { AppModule } from './modules/app.module';
import { speechToTextCronJob } from './utilities/speech-to-text/speech-to-text.cron';
const { json } = pkg;

const ENVIRONMENT = process.env.ENVIRONMENT || 'development';
const SERVICE_LIST = CONFIG.SERVER_LIST[ ENVIRONMENT ];
const port: string | number = process.env.PORT || CONFIG.APP.PORTS.GATEWAY;
const localHealthCheck: string = CONFIG.APP.URLS.HEALTH_CHECK;

console.log(``);
console.log(
  `**********************************************************************************************`
);
console.log(
  `Starting Gateway API Server for '${ENVIRONMENT}' environment`
);
console.log(
  `**********************************************************************************************`
);
console.log(``);

const options: jwt.VerifyOptions = {
  audience: CONFIG.AUTH.AUDIENCE,
  issuer: CONFIG.AUTH.CLAIM_ISSUER,
  algorithms: [ 'RS256' ],
};

function getKeyFromAuth0(header, cb) {
  const jwksUri = `${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`;

  const client = jwksClient({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri,
  });

  client.getSigningKey(
    header.kid,
    function (err: Error, key: jwksClient.SigningKey) {
      if (err) {
        console.log(err);
      }

      const signingKey = key?.getPublicKey();

      cb(null, signingKey);
    }
  );
}

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest(_data) {
    // Check auth
    return;
  }
}

const app = express();

const httpServer = http.createServer(app);

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'mm', url: SERVICE_LIST.MM },
      { name: 'ourremoteservice', url: SERVICE_LIST.OUR_SERVICE },
    ],
  }),
  buildService({ name, url }) {
    console.log(`implementing service: | ${name}: ${url}`);

    if (url === SERVICE_LIST.MM) {
      return new LocalGraphQLDataSource(
        applyMiddleware(
          buildSubgraphSchema(
            {
              typeDefs: AppModule.typeDefs,
              resolvers: AppModule.resolvers,
            },
          )
        )
      );
    }

    return new AuthenticatedDataSource({ url });
  },

});
const server = new ApolloServer({
  gateway, //If I change to schema:AppModule.createSchemaForApollo() it can work
  plugins: [
    ApolloServerPluginCacheControl({
      defaultMaxAge: 0,
    }),
    process.env.NODE_ENV === 'production'
      ? ApolloServerPluginLandingPageProductionDefault({
        graphRef: 'my-graph-id@my-graph-variant',
        footer: false,
      })
      : ApolloServerPluginLandingPageLocalDefault({ footer: false }),
    ApolloServerPluginDrainHttpServer({ httpServer })
  ],
  cache: 'bounded',
  introspection: true,
  csrfPrevention: true,
  formatError: (formattedError: GraphQLFormattedError, error: any) => {
    console.log('FORMATTED ERROR -----> ', formattedError);
    console.log('ERROR -----> ', error);
    return formattedError;
  }
});


speechToTextCronJob();

async function startServer() {
  app.use(graphqlUploadExpress());
  await server.start();
  app.use(
    '/graphql',
    cors<cors.CorsRequest>(),
    json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
        CONFIG.SESSION.TOKEN = req.headers.authorization;
        const prisma = new PrismaClient();
        try {
          const token = req.headers.authorization?.split(' ')?.[ 1 ] || '';
          if (!token) {
            return { token, user: {} };
          }
          let user: any = await new Promise((resolve, _reject) => {
            jwt.verify(token, getKeyFromAuth0, options, (err, decoded) => {
              if (err) {
                console.log('error: ', err);
              }
              resolve(decoded);
            });
          });
          // Find user
          if (user && user.email) {
            const existUser = await prisma.user.findFirst({
              where: {
                email: user.email,
                active: true,
              },
            });

            if (existUser) {
              const auth0Id = user.sub;
              user = existUser;
              await prisma.user.update({
                where: {
                  email: user.email,
                },
                data: {
                  lastLogin: new Date(),
                  auth0Id,
                },
              });
            }
          }
          await prisma.$disconnect();
          return { token, user };
        } catch (error) {
          await prisma.$disconnect();
          console.log('FROM APOLLO SERVER CONTEXT | AUTHENTICATED ERROR: ', error);
        }
      }
    }),
  );
  await new Promise<void>((resolve) => httpServer.listen({ port }, resolve));
  console.log(``);
  console.log(``);
  console.log(`Mentor Method Gateway API Server ready`);
  console.log(`PORT: ${port}`);
  console.log(`ENVIRONMENT: ${ENVIRONMENT}`);
  console.log(
    `Try your health check at: /.well-known/apollo/server-health`
  );
  console.log(``);
  console.log(``);
  console.log('v12.13');
}

startServer();

After I start a server and send request with result below


it return null data. but it should return { id: ‘1’, username: ‘@ava’ }. after checking I found that the resolver isn’t call. Pls tell me if you need more info. Thank you.

Can you isolate the change to either the Apollo Server upgrade OR the switch to buildSubgraphSchema ?
I have do both Apollo Server upgrade and the switch to buildSubgraphSchema

Can you share your Apollo Server config?

import {
  ApolloGateway, IntrospectAndCompose,
  LocalGraphQLDataSource,
  RemoteGraphQLDataSource
} from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault } from '@apollo/server/plugin/landingPage/default';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { PrismaClient } from '@prisma/client';
import pkg from 'body-parser';
import cors from 'cors';
import express from 'express';
import { GraphQLFormattedError } from 'graphql';
import { applyMiddleware } from 'graphql-middleware';
import { gql } from 'graphql-modules';
import graphqlUploadExpress from "graphql-upload/graphqlUploadExpress.js";
import http from 'http';
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import 'module-alias/register';
import "reflect-metadata";
import '../prisma/seed';
import { CONFIG } from './config';
import { AppModule } from './modules/app.module';
import { speechToTextCronJob } from './utilities/speech-to-text/speech-to-text.cron';
const { json } = pkg;

const ENVIRONMENT = process.env.ENVIRONMENT || 'development';
const SERVICE_LIST = CONFIG.SERVER_LIST[ ENVIRONMENT ];
const port: string | number = process.env.PORT || CONFIG.APP.PORTS.GATEWAY;
const localHealthCheck: string = CONFIG.APP.URLS.HEALTH_CHECK;

console.log(``);
console.log(
  `**********************************************************************************************`
);
console.log(
  `Starting Gateway API Server for '${ENVIRONMENT}' environment`
);
console.log(
  `**********************************************************************************************`
);
console.log(``);

const options: jwt.VerifyOptions = {
  audience: CONFIG.AUTH.AUDIENCE,
  issuer: CONFIG.AUTH.CLAIM_ISSUER,
  algorithms: [ 'RS256' ],
};

function getKeyFromAuth0(header, cb) {
  const jwksUri = `${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`;

  const client = jwksClient({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri,
  });

  client.getSigningKey(
    header.kid,
    function (err: Error, key: jwksClient.SigningKey) {
      if (err) {
        console.log(err);
      }

      const signingKey = key?.getPublicKey();

      cb(null, signingKey);
    }
  );
}

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest(_data) {
    // Check auth
    return;
  }
}

const app = express();

const httpServer = http.createServer(app);

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'mm', url: SERVICE_LIST.MM },
      { name: 'ourremoteservice', url: SERVICE_LIST.OUR_SERVICE },
    ],
  }),
  buildService({ name, url }) {
    console.log(`implementing service: | ${name}: ${url}`);

    if (url === SERVICE_LIST.MM) {
      return new LocalGraphQLDataSource(
        applyMiddleware(
          buildSubgraphSchema(
            {
              typeDefs: AppModule.typeDefs,
              resolvers: AppModule.resolvers,
            },
          )
        )
      );
    }

    return new AuthenticatedDataSource({ url });
  },

});
const server = new ApolloServer({
  gateway, //If I change to schema =AppModule.createSchemaForApollo() is work fine
  plugins: [
    ApolloServerPluginCacheControl({
      defaultMaxAge: 0,
    }),
    process.env.NODE_ENV === 'production'
      ? ApolloServerPluginLandingPageProductionDefault({
        graphRef: 'my-graph-id@my-graph-variant',
        footer: false,
      })
      : ApolloServerPluginLandingPageLocalDefault({ footer: false }),
    ApolloServerPluginDrainHttpServer({ httpServer })
  ],
  cache: 'bounded',
  introspection: true,
  csrfPrevention: true,
  formatError: (formattedError: GraphQLFormattedError, error: any) => {
    console.log('FORMATTED ERROR -----> ', formattedError);
    console.log('ERROR -----> ', error);
    return formattedError;
  }
});


speechToTextCronJob();

async function startServer() {
  app.use(graphqlUploadExpress());
  await server.start();
  app.use(
    '/graphql',
    cors<cors.CorsRequest>(),
    json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
        CONFIG.SESSION.TOKEN = req.headers.authorization;
        const prisma = new PrismaClient();
        try {
          const token = req.headers.authorization?.split(' ')?.[ 1 ] || '';
          if (!token) {
            return { token, user: {} };
          }
          let user: any = await new Promise((resolve, _reject) => {
            jwt.verify(token, getKeyFromAuth0, options, (err, decoded) => {
              if (err) {
                console.log('error: ', err);
              }
              resolve(decoded);
            });
          });
          // Find user
          if (user && user.email) {
            const existUser = await prisma.user.findFirst({
              where: {
                email: user.email,
                active: true,
              },
            });

            if (existUser) {
              const auth0Id = user.sub;
              user = existUser;
              await prisma.user.update({
                where: {
                  email: user.email,
                },
                data: {
                  lastLogin: new Date(),
                  auth0Id,
                },
              });
            }
          }
          await prisma.$disconnect();
          return { token, user };
        } catch (error) {
          await prisma.$disconnect();
          console.log('FROM APOLLO SERVER CONTEXT | AUTHENTICATED ERROR: ', error);
        }
      }
    }),
  );
  await new Promise<void>((resolve) => httpServer.listen({ port }, resolve));
  console.log(``);
  console.log(``);
  console.log(`Mentor Method Gateway API Server ready`);
  console.log(`PORT: ${port}`);
  console.log(`ENVIRONMENT: ${ENVIRONMENT}`);
  console.log(
    `Try your health check at: /.well-known/apollo/server-health`
  );
  console.log(``);
  console.log(``);
  console.log('v12.13');
}

startServer();

Can you share more details about what isn’t working? Any errors you’re seeing when you send a request?
The server is running but when i send a request ‘me’ to server, the ressult is null, it should return { id: ‘1’, username: ‘@ava’ } the request in the image below


After checking, I found that the resolver isn’t call. Pls let me know if you need more info. Thanks you so much

Could you pls help me to check it. Pls tell me if you need more information. Thanks you so much.