slicknode/graphql-query-complexity

How to use it with Apollo Server?

FluorescentHallucinogen opened this issue · 13 comments

Is it possible to use it with Apollo Server? More precisely with apollo-server-express?

If yes, could you please add the sample code to README.md?

ivome commented

It's definitely possible. I just briefly looked into the apollo-server-express code. You can create the validation rule similar to the example give in the README for the use with express-graphql.

(Not tested):

import { graphqlExpress } from 'apollo-server-express';
 
const myGraphQLSchema = // ... define or import your schema here!
const PORT = 3000;
 
const app = express();
 
// bodyParser is needed just for POST.
app.use('/graphql', bodyParser.json(), graphqlExpress((req, res) => {
  // Extract variables from req object here to pass to validation rule
  return { 
    schema: myGraphQLSchema,
    validationRules: [
      // Add queryComplexity configuration here
    ]
  }
}));

If you have a full working version, a PR to the README would be appreciated.

@ivome
The new Apollo Server 2.0 has changed the API and now ApolloServer constructor accept a config as a parameter, when you can place validationRules but they only take ValidationContext as a callback function argument, so there's no req.variables access.

Can we somehow use getVariableUsages(node: NodeWithSelectionSet): ReadonlyArray<VariableUsage>; method on context to get this?

ivome commented

@19majkel94 Unfortunately this does not work as far as I can tell. This only returns the definitions of the variable nodes, not the actual values. The problem is that the variable values are not part of the ValidationContext in graphql-js.

I can only think of two options:

  1. Add the variables to the ValidationContext in graphql-js, not sure how big of a project that would be and if it's even desired.
  2. Change apollo-server so that it supports the dynamic creation of configuration options based on the request.

I haven't looked at the source code of ApolloServer 2 in detail, but nr 2 is probably the best option as this also opens up the possibility to write other dynamic validation rules (restrict parts of the schema for certain IP blocks / user agents etc.)

Or any other ideas?

Change apollo-server so that it supports the dynamic creation of configuration options based on the request. I haven't looked at the source code of ApolloServer 2 in detail...

I've looked at it and they just pass the array of validators to graphql-js, so the only option is to create a wrapper to inject the additional "context" using closure.

I will open an issue on apollo-server repo for that 😉

ivome commented

I just published version v0.3.0 of this library which adds a helper function getComplexity to calculate the complexity outside of the validation phase. This could be used pretty much anywhere where the query and variables are available:

https://github.com/slicknode/graphql-query-complexity#calculate-query-complexity

How to use validation for graphql-upload multipart request?

curl localhost:12250/graphql -F operations='{ "query": "mutation ($file: Upload!) { uploadImage(file: $file) }", "variables": { "file": null } }' -F map='{ "0": ["variables.file"] }' -F 0=@file.txt {"errors":[{"message":"Argument \"file\" of required type \"Upload!\" was provided the variable \"$file\" which was not provided a runtime value.","locations":[{"line":1,"column":47}],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED","exception":{"stacktrace":["GraphQLError: Argument \"file\" of required type \"Upload!\" was provided the variable \"$file\" which was not provided a runtime value."," at Object.getArgumentValues (node_modules/graphql/execution/values.js:181:15)"," at node_modules/graphql-query-complexity/src/QueryComplexity.ts:198:24"," at Array.reduce (<anonymous>)"," at QueryComplexity.nodeComplexity (node_modules/graphql-query-complexity/src/QueryComplexity.ts:182:56)"," at QueryComplexity.onOperationDefinitionEnter (node_modules/graphql-query-complexity/src/QueryComplexity.ts:144:33)"," at Object.enter (node_modules/graphql/language/visitor.js:332:29)"," at Object.enter (node_modules/graphql/language/visitor.js:383:25)"," at visit (node_modules/graphql/language/visitor.js:250:26)"," at Object.validate (node_modules/graphql/validation/validate.js:63:22)"," at validate (node_modules/apollo-server-core/src/requestPipeline.ts:423:22)"]}}}]}

@ivome
With getComplexity I was able to integrate this library with Apollo Server 💪
Here is a working example:
https://github.com/19majkel94/type-graphql/blob/4501867fffe3e6f5b3e71af0b71651efcd48d9c3/examples/query-complexity/index.ts#L16-L64

For anyone who comes across this, looking to define cost complexity using the fieldExtensionsEstimator, but defining their schema using apollo typeDefs and resolvers I've created an example at digicatapult/graphql-complexity-experiment based on the example given by @MichalLytek.

Unfortunately my example relies what I believe is unsupported/undocumented behaviour in graphql-tools. I've created an issue to hopefully add support for this (ardatan/graphql-tools#1279)

ivome commented

I'm going to close this here as this can be solved outside of this library like mentioned above: https://github.com/MichalLytek/type-graphql/blob/4501867fffe3e6f5b3e71af0b71651efcd48d9c3/examples/query-complexity/index.ts#L16-L64

If someone wants to create an npm package with an apollo server plugin, I'm happy to accept a PR with a link in the README.

Small refactor of @MichalLytek's file that made it into a plugin https://gist.github.com/reconbot/37ccd8b8fcdfb78ff6a16a7055edf103

First of all, I'm sorry to continue a closed question here

But I'm really in pain
I'm using Nest combined with Gql (Apollo) + complexity
But it seems that the document is not comprehensive, https://docs.nestjs.com/graphql/complexity

I looked up how graphql-query-complexity is used
I don't know how to get a schema and I'm not familiar with gql and how it works and I just want to use this feature, right

// Nest
GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      cors: corsOptions,
      path: gqlPath,
      playground: !isPrd,
      plugins: [{
        // How to get a schema => https://github.com/MichalLytek/type-graphql/blob/4501867fffe3e6f5b3e71af0b71651efcd48d9c3/examples/query-complexity/index.ts#L30
      }],
      cache: 'bounded', // 解决生产报错 Persisted queries are enabled and are using an unbounded cache. Your server is vulnerable to denial of service attacks via memory exhaustion. Set `cache: "bounded"` or `persistedQueries: false` in your ApolloServer constructor, or see https://go.apollo.dev/s/cache-backends for other alternatives.
      autoSchemaFile: path.resolve(__dirname, 'schema/index.gql'), // true 在内存中
      context(ctx) {
        return ctx;
      },
    }),

After much effort I finally found a working example:nestjs/graphql#373