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

Support batched GraphQL queries #510

Closed
antoniosZ opened this issue Dec 14, 2020 · 28 comments · Fixed by #1982
Closed

Support batched GraphQL queries #510

antoniosZ opened this issue Dec 14, 2020 · 28 comments · Fixed by #1982
Assignees

Comments

@antoniosZ
Copy link

Environment

Name Version
msw 0.24.2
browser chrome Version 87.0.4280.88 (Official Build) (x86_64)
OS macOS Big Sur 11.0.1 (20B29)

Request handlers

export const handlers = [
  graphql.operation((req, res, ctx) => {
    console.log({ req, res, ctx });
    return res(
      ctx.data({
        user: {
          username: 'test',
          firstName: 'John',
        },
      })
    );
  }),

  graphql.query('getTest', (req, res, ctx) => {
    return res(
      ctx.data({
        user: {
          username: 'test',
          firstName: 'John',
        },
      })
    );
  }),
];

Actual request

https://www.apollographql.com/docs/link/links/batch-http/
Using Apollo Client to perform a normal batch query

ie.

import { BatchHttpLink } from "apollo-link-batch-http";
const link = new BatchHttpLink({ uri: "/graphql" });

Current behavior

batch queries are not getting intercepted but if I switch from BatchHttpLink to a regular HttpLink, the interception works as expected.

Expected behavior

batch queries get intercepted

@antoniosZ antoniosZ added bug Something isn't working scope:browser Related to MSW running in a browser labels Dec 14, 2020
@antoniosZ
Copy link
Author

the only way I got graphql batch requests to be intercepted, is by using rest with the full request URL, like so:

rest.post('https://my-graphql-server/endpoint', (req, res, ctx) => {
    ...
}

and then I have to manually analyze the req.body[] and handle/mock the request.
meaning, I lose all the graphql declarative/functionality mswjs provides...

@msutkowski
Copy link
Member

@antoniosZ Do you have a repro for this? I haven't tried this feature myself, but I imagine it might have to be linked? https://mswjs.io/docs/api/graphql/link#examples

@antoniosZ
Copy link
Author

@msutkowski I tried linking and that didn't help. I'll see if I can quickly create a repro

@antoniosZ
Copy link
Author

@msutkowski Unfortunately, I could not find a public graphql server to use for the repro, that supports query batching. please let me know if you have one available and then I can create the demo project that reproduces it... thank you

@msutkowski
Copy link
Member

I'm not a graphQL user personally but I can try to take a look at this later and set up a server locally. It's possible that somebody will beat me to this but I'll try to get to it by tomorrow

@msutkowski
Copy link
Member

Ah, the issue here is that msw doesn't currently support batch operations. BatchLink is sending over an array of operations, which isn't going to resolve. If you're able to, I'm sure @kettanaito would accept a PR if you have the time to implement this? If not, perhaps someone can jump in :)

@kettanaito kettanaito added the help wanted Extra attention is needed label Dec 15, 2020
@antoniosZ
Copy link
Author

antoniosZ commented Dec 15, 2020

@msutkowski this is exactly what is happening. It posts an array, instead of an object...
I wish I could say I have the time to work on it, but I doubt I will be able to get to it.

@msutkowski
Copy link
Member

@antoniosZ can you test it out against this preview of the build? https://ci.codesandbox.io/status/mswjs/msw/pr/513/builds/83287 - or npm i https://pkg.csb.dev/mswjs/msw/commit/d19bb4e8/msw and let me know if it works as expected? thanks!

@antoniosZ
Copy link
Author

thank you @msutkowski but unfortunately, it does not work.

It appears that the problem is here:
https://github.com/mswjs/msw/pull/513/files#diff-6178a21f02701c9f8fa7ae2cc995b08165615a3afddac155fb850e355269897aR154

@msutkowski
Copy link
Member

@antoniosZ Sorry about that, there is a little more work to do here for handling the arrays. I'll get it taken care of today :)

@antoniosZ
Copy link
Author

thank you for working on this @msutkowski! Is there a new commit that you would like me to test?

@kettanaito kettanaito added this to the GraphQL support milestone Dec 25, 2020
@dkhuntrods
Copy link

@msutkowski We're interested in a resolution for this as well in our organisation.

Unfortunately we have found errors in TS integration beyond version 0.21.2, so we're fixed to that at the moment.
We're considering forking from that version and then building on the work you've currently put in to resolve this.
Have you any thoughts about or objections to this approach?

Many thanks!

@logaretm
Copy link

logaretm commented Mar 7, 2021

Hello, I maintain a GraphQL client for Vue.js and used the following workaround for the batched requests test.

As @antoniosZ pointed out, it requires intercepting it as a rest request, but I utilized the handlers you typically define earlier

export const handlers = [
  // some graphql handlers ...
  // add this to the end of GQL handlers
  rest.post('https://test.com/graphql', async (req, res) => {
    if (!Array.isArray(req.body)) {
      throw new Error('Unknown operation');
    }

    // map the requests to responses using their handlers
    const data = await Promise.all(
      req.body.map(async op => {
        const partReq = { ...req, body: op };
        const handler = handlers.find(h => h.test(partReq));
        // no handler matched that operation
        if (!handler) {
          return Promise.reject(new Error(`Cannot handle operation ${op}`));
        }

        // execute and return the response-like object
        return handler.run(partReq);
      })
    );

    return res(res => {
      res.headers.set('content-type', 'application/json');

      // responses need extraction from the individual requests
      // and we stitch them into a single array response
      res.body = JSON.stringify(
        data.map(d => {
          return JSON.parse(d?.response?.body) || {};
        })
      );

      return res;
    });
  }),
];

I realize this might not cover most cases, but works for me for now.

@kettanaito
Copy link
Member

@msutkowski did some incredible job on the initial support phase. We've merged a major refactoring to how the request handlers work internally, so that pull request needs to be adjusted. I will try to resolve the conflicts and leave it at the same state for any volunteers to collaborate.

Thank you for your interest in this. We'd love to make batched operations support happen.

@innowhat
Copy link

innowhat commented May 1, 2021

I'm experiencing the same issue, any fix coming up ?

@phbou72
Copy link

phbou72 commented Feb 1, 2022

Would also like a fix for this! Spent a good 2h on this before figuring it was a bug!

@JoeyAtSS
Copy link

JoeyAtSS commented Feb 2, 2022

bump

@snovos
Copy link

snovos commented Feb 10, 2022

Ok, so its been a year and still no fix?

@n2o1988
Copy link

n2o1988 commented Jul 1, 2022

Hi everyone! I was wondering if there are any plans to continue the work to support batched queries. Seems like the linked PR is kind of stale now.
We're loving msw for rest APIs and spent a good few hours troubleshooting why the graphql intercepters wouldn't work before landing on this issue. Right now we have to go back to use apollo's MockedProvider but I'd love for this to go through, eventually!

@n2o1988
Copy link

n2o1988 commented Jul 22, 2022

bump

@ifeanyiisitor
Copy link

Hi all. Any progress on this?

@weshicks
Copy link

Hey @kettanaito! First of all, thank you for all of the work you've put into this really useful tool!

I was wondering if this bug was on your roadmap to resolve, or if it was available for someone to pick up and complete? This thread seems to have stagnated (with a few requests for updates), and #513 seems to have gone stale.

If this is something you're still looking for help on, I may be able to dedicate some time to digging in on this -- having this resolved would save me a ton of time in my app!

@dani-a-dolan
Copy link

@kettanaito any progress on this?

@kettanaito kettanaito changed the title apollo-link-batch-http (batch) queries are not getting intercepted Support batched GraphQL queries Jan 20, 2024
@kettanaito
Copy link
Member

Sharing my thoughts on the batched GraphQl queries since I had a minute to look into this question.

Batched queries are non-standard

First, I can hardly see support for batched GraphQL queries landing in MSW for the following reason: query batching isn't a part of the GraphQL specification. It's a technique used by GraphQL clients to enhance performance. This means that each GraphQL client is free to implement and interpret batched queries as it wishes.

For example, Apollo and batch-execute implement two entirely different syntaxes for batched queries:

// Apollo
[
  { data: { user: value },
  { data: { product: value },
  { data: { user: value }
]

// batch-execute
{
  user_0: value,
  product_0: value,
  user_1: value
}

As one would expect from the lack of standard, the clients will dictate all sorts of formats that each client finds the best for them. MSW, however, cannot cater to particular clients, it's against our core philosophy. The library has shipped zero framework/client-specific code for 5 years, and I won't see that changed.

In practice, the lack of a standard batched query format means MSW has to guess what GraphQL client you may be using. Inevitably, this will lead to users demanding support for their client of choice and how batched queries are implemented there. This is a slippery slope of failed expectations I'd rather not see us even step a foot at.

This issue quickly becomes apparent once you try to apply a batched query resolution to the request/response contract. 1 request handler is responsible for controlling 1 request. Naturally, you want to keep the query/response collocation flat because that's how it is in your actual GraphQL server (batching is an entirely a GraphQL client's feature; the client wraps/unwraps the requests, your server doesn't know anything about those batches). Suddenly, a single request contains multiple GraphQL queries that have to be (1) parsed, (2) matched against the request handlers; (3) resolved. A single GraphQL request handler cannot do it so the next idea is to lift this resolution up to handleRequest/getResponse pipelines. But that doesn't work either because now we need to preemptively parse every HTTP request just to check if it's potentially a GraphQL request with a batched query even if you don't have any GraphQL handlers at all. This is an apparent design flaw and an indication that such logic doesn't belong to MSW.

How to handle batched queries then?

I suggest you detect and unwrap the batched queries in your network description. I highly suggest you do this on your end because:

  1. You know what GraphQL client you are using so you know which format of batched queries to expect in the intercepted request's body;
  2. You know that you are using GraphQL for a fact; MSW doesn't know that. It has no way of detecting that either (and it shouldn't do such things anyway).

I already have both use cases implemented and passing tests in here: #1982. Once this is finalized, I will add this as a recipe to the docs so it's clear how to approach mocking batched GraphQL queries.

@kettanaito kettanaito assigned kettanaito and unassigned msutkowski Jan 20, 2024
@kalabro
Copy link

kalabro commented Jan 20, 2024

Could graphql-over-http serve as a standard?

@kettanaito
Copy link
Member

kettanaito commented Jan 21, 2024

@kalabro, we can but you have to consider that spec itself is a draft and can change as more and more people contribute and refine it. Batching is also not mentioned in their public draft, and given it's in the /rfc directory, it seems it's only a proposal at this stage.

The important bit is this: you can achieve support for batched GraphQL queries trivially on your side. You are also in full context and awareness of what you're using so you don't have to guess, unlike MSW. This is a win-win for both sides and this is the direction we are taking on such features. I'm finalizing the tests and the updates to the docs to share more details on how to do that.

Interestingly enough, I can see that Apollo uses this array-of-queries while batched-execute prepends field aliases to support multiple same fields in a single query. At first, I liked the latter approach more because you get a single query that you can resolve against the schema and be done with it (with the client then mapping the right aliases to the right operations). But as I kept testing, I think I actually prefer the array-of-queries a bit more. You cannot resolve it against a schema manually but you can extract each individual query and resolve it against the list of request handlers in MSW and then return the accumulated response.

@kettanaito kettanaito added feature needs:discussion api:graphql and removed bug Something isn't working needs:reproduction scope:browser Related to MSW running in a browser labels Jan 21, 2024
@kettanaito
Copy link
Member

Mock batched GraphQL Queries

@kettanaito
Copy link
Member

Released: v2.1.3 🎉

This has been released in v2.1.3!

Make sure to always update to the latest version (npm i msw@latest) to get the newest features and bug fixes.


Predictable release automation by @ossjs/release.

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