vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.1k stars 26.72k forks source link

Multiple Graphql API Routes example #16320

Closed samuelcastro closed 7 months ago

samuelcastro commented 4 years ago

Feature request

Is your feature request related to a problem? Please describe.

It'd be super helpful to have an example of a multiple api routes setup using graphql, all examples I can see are considering just one single api route, but in a real world we usually will need multiple APIs, specially when working with serverless functions. Maybe there is some sort of limitation around multiples api server with graphql that I'm not aware of.

Additional context

The solution described in this article would be helpful to connect multiple graphql endpoints into one single graphql instance, something like:

// Create First Link
const firstLink = new HttpLink({
  uri: 'https://www.firstsource.com/api',
  headers: yourHeadersHere,
  // other link options...
});

// Create Second Link
const secondLink = new HttpLink({
  uri: 'https://www.secondsource.com/api',
  headers: yourHeadersHere
  // other link options...
});

and then:

function create (initialState) {
  return new ApolloClient({
    connectToDevTools: process.browser,
    ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
    link: ApolloLink.split(
      operation => operation.getContext().clientName === "second", // Routes the query to the proper client
      secondLink,
      firstLink
    ),
    cache: new InMemoryCache().restore(initialState || {})
  })
}

What's your thoughts?

samuelcastro commented 4 years ago

Another solution would be: https://github.com/habx/apollo-multi-endpoint-link

new ApolloClient({
    link: ApolloLink.from([
      new MultiAPILink({
          endpoints: {
              housings: 'https://housings.api',
              projects: 'https://projects.api',
              ...
          },
          createHttpLink: () => new HttpLink({ ... }),
        }),
    ])
  })

More info here: https://www.habx.com/tech/micro-graphql-schema

samuelcastro commented 4 years ago

Or maybe an example showing up how to integrate Apollo Server Federation

eric-burel commented 3 years ago

For the record I use the split approach in production in my projects, with 3 services, it works perfectly well. It allows our backend team to postpone the creation of a federated GraphQL API until we really really need it, and work service by service instead. Limitation is that you can't have the same type name in different API (it would break Apollo Client caching), so this is meant to handle API you actually control.

This is the generalization for N links, based on a comment of the article (which I can't access right now sadly) from @naveennazimudeen: https://github.com/VulcanJS/vulcan-next/blob/devel/src/pages/docs/recipes.md#smart-replacement-of-the-http-link

Not sure about the MultiAPILink approach, I can't tell if it has the same limitation. I don't like using directives though, I prefer to play on the context.

DanielAcostaRoa commented 2 years ago

Hi,

I am trying to integrate multiple api routes using graphql. In the following repository I have a toy example where I expose two different services: products and users.

I originally thought of exposing a single file called [...graphql].ts that would capture all the API paths, but I don't know if doing this exposes it as a single serverless function, if so it might affect the performance of all services. That's why I chose to expose different paths in different files inside the api/graphql directory, this way I ensure that each serverless function contains only the packages it needs and that it scales on its own demand. (Is this argument correct?)

Additionally, the directory contains one more path called "super", which in the context of the apollo federation is the super-graph that contains the union of the "products" and "users" subgraphs. This is for development purposes only, in order to view a single graph in development, in production I make sure to omit it.

I recently started exploring apollo federation and finding documentation on it has been difficult, I would appreciate any reference on best practices for integrating nextjs and federated services with apollo federation.

lcanavesio commented 1 year ago

Hi, in my case I use links created with @graphql-codegen, you can use clientName.

//codegenInbox.ts import type { CodegenConfig } from '@graphql-codegen/cli' const config: CodegenConfig = { schema:${process.env.NEXT_PUBLIC_API_INBOX}/graphql, documents: './{components, graphql}/**/*.{ts,tsx}', overwrite: true, generates: { './graphql/types.tsx': { plugins: ['typescript', 'typescript-react-apollo','typescript-operations'], config: { defaultBaseOptions: { context: { clientName: "inboxLink" }, }, withMutationFn: true, withHOC: false, withHooks: true, withComponent: false }, }, }, } export default config

in package.json add: "generate-inbox": "graphql-codegen -r dotenv/config codegenInbox.ts" ...

//Add another configs for yours apis

//create apollo.ts

`import { ApolloClient, createHttpLink, InMemoryCache, split, } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; import { getMainDefinition } from '@apollo/client/utilities'; import { createClient } from 'graphql-ws';

export const config = { inboxLink: { url: ${process.env.NEXT_PUBLIC_INBOX_MAIL_API}/graphql, hasWebSocket: true, }, productLink: { url: ${process.env.NEXT_PUBLIC_PRODUCT}/graphql, hasWebSocket: false, }, };

export default function inicializar(token: string) {

const createHttpLinkForUrl = (url: string) => { return url ? createHttpLink({ uri: url, credentials: 'include', }) : null; };

const createGraphQLWsLinkForUrl = (url: string) => { return new GraphQLWsLink(createClient({ url: url.replace('http', 'ws'), connectionParams: { Authorization: Bearer ${token}, credentials: 'include', }, })); };

const createSplitLink = (operationName: string, httpLink: any, previousLink: any) => { return split( (operation) => operation.getContext().clientName === operationName, httpLink, previousLink, ); };

const createCombinedHttpLinks = () => { let combinedHttpLinks = null;

Object.entries(config).forEach(([operationName, { url }]) => {
  if (url) {
    combinedHttpLinks = createSplitLink(operationName, createHttpLinkForUrl(url), combinedHttpLinks);
  }
});

return combinedHttpLinks;

};

const createWebSocketSplitLink = (operationName: string, webSocketLink: any, previousLink: any) => { return split( (operation) => operation.getContext().clientName === operationName, webSocketLink, previousLink, ); }; const createCombinedWebSocketLink = () => { let combinedWsLink = null;

Object.entries(config).forEach(([operationName, { url, hasWebSocket }]) => {
  if (hasWebSocket && url) {
    combinedWsLink = createWebSocketSplitLink(operationName, createGraphQLWsLinkForUrl(url), combinedWsLink);
  }
});

return combinedWsLink;

};

const authLink = setContext((_, { headers }) => {
return { headers: { ...headers, authorization: Bearer ${token},

  },
};

});

const link = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, createCombinedWebSocketLink(), authLink.concat(createCombinedHttpLinks()), );

return new ApolloClient({ link: authLink.concat(link), cache: new InMemoryCache().restore({}), }); }`