Skip to content

Commit 8d2b674

Browse files
committed
feat: add query cost to northwind schema
1 parent 408f15e commit 8d2b674

File tree

13 files changed

+184
-20
lines changed

13 files changed

+184
-20
lines changed

examples/northwind/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
import dedent from 'dedent';
66
import schema from './schema';
7+
import { initQueryComplexityPlugin } from './queryCostPlugin';
78

89
export default {
910
uri: '/northwind',
1011
schema: schema,
12+
plugins: [initQueryComplexityPlugin({ schema, maxComplexity: 5000 })],
1113
title: 'Northwind: complex schema with 8 models',
1214
description:
1315
'This is a sample data of some trading company, which consists from 8 models. All models has cross-relations to each other. This schema used in <b><a href="https://nodkz.github.io/relay-northwind/" target="_blank">Relay example app <span class="glyphicon glyphicon-new-window"></span></a></b>',

examples/northwind/models/category.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ export const Category = model('Category', CategorySchema);
2626

2727
export const CategoryTC = composeWithMongoose<any>(Category);
2828

29+
CategoryTC.getResolver('connection').extensions = {
30+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
31+
};
32+
CategoryTC.getResolver('pagination').extensions = {
33+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
34+
};
35+
CategoryTC.getResolver('findMany').extensions = {
36+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
37+
};
38+
2939
CategoryTC.addRelation('productConnection', {
3040
resolver: () => ProductTC.getResolver('connection'),
3141
prepareArgs: {

examples/northwind/models/customer.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ export const Customer = model('Customer', CustomerSchema);
3030

3131
export const CustomerTC = composeWithMongoose<any>(Customer);
3232

33+
CustomerTC.getResolver('connection').extensions = {
34+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
35+
};
36+
CustomerTC.getResolver('pagination').extensions = {
37+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
38+
};
39+
CustomerTC.getResolver('findMany').extensions = {
40+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
41+
};
42+
3343
CustomerTC.addRelation('orderConnection', {
3444
resolver: () => OrderTC.getResolver('connection'),
3545
prepareArgs: {

examples/northwind/models/employee.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ export const Employee = model('Employee', EmployeeSchema);
6565

6666
export const EmployeeTC = composeWithMongoose<any>(Employee);
6767

68+
EmployeeTC.getResolver('connection').extensions = {
69+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
70+
};
71+
EmployeeTC.getResolver('pagination').extensions = {
72+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
73+
};
74+
EmployeeTC.getResolver('findMany').extensions = {
75+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
76+
};
77+
6878
const findManyResolver = EmployeeTC.getResolver('findMany').addFilterArg({
6979
name: 'fullTextSearch',
7080
type: 'String',

examples/northwind/models/order.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ export const Order = model('Order', OrderSchema);
5151

5252
export const OrderTC = composeWithMongoose<any>(Order);
5353

54+
OrderTC.getResolver('connection').extensions = {
55+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
56+
};
57+
OrderTC.getResolver('pagination').extensions = {
58+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
59+
};
60+
OrderTC.getResolver('findMany').extensions = {
61+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
62+
};
63+
5464
OrderTC.addRelation('customer', {
5565
resolver: () => CustomerTC.getResolver('findOne'),
5666
prepareArgs: {

examples/northwind/models/product.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ export const Product = model('Product', ProductSchema);
3737

3838
export const ProductTC = composeWithMongoose<any>(Product);
3939

40+
ProductTC.getResolver('connection').extensions = {
41+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
42+
};
43+
ProductTC.getResolver('pagination').extensions = {
44+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
45+
};
46+
ProductTC.getResolver('findMany').extensions = {
47+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
48+
};
49+
4050
const extendedResolver = ProductTC.getResolver('findMany').addFilterArg({
4151
name: 'nameRegexp',
4252
type: 'String',

examples/northwind/models/region.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ export const Region = model('Region', RegionSchema);
3535

3636
export const RegionTC = composeWithMongoose<any>(Region);
3737

38+
RegionTC.getResolver('connection').extensions = {
39+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
40+
};
41+
RegionTC.getResolver('pagination').extensions = {
42+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
43+
};
44+
RegionTC.getResolver('findMany').extensions = {
45+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
46+
};
47+
3848
RegionTC.addRelation('employees', {
3949
resolver: () => EmployeeTC.getResolver('findMany'),
4050
prepareArgs: {

examples/northwind/models/shipper.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ export const Shipper = model('Shipper', ShipperSchema);
2323

2424
export const ShipperTC = composeWithMongoose<any>(Shipper);
2525

26+
ShipperTC.getResolver('connection').extensions = {
27+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
28+
};
29+
ShipperTC.getResolver('pagination').extensions = {
30+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
31+
};
32+
ShipperTC.getResolver('findMany').extensions = {
33+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
34+
};
35+
2636
ShipperTC.addRelation('orderConnection', {
2737
resolver: () => OrderTC.getResolver('connection'),
2838
prepareArgs: {

examples/northwind/models/supplier.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export const Supplier = model('Supplier', SupplierSchema);
2929

3030
export const SupplierTC = composeWithMongoose<any>(Supplier);
3131

32+
SupplierTC.getResolver('connection').extensions = {
33+
complexity: ({ args, childComplexity }) => childComplexity * (args.first || args.last || 20),
34+
};
35+
SupplierTC.getResolver('pagination').extensions = {
36+
complexity: ({ args, childComplexity }) => childComplexity * (args.perPage || 20),
37+
};
38+
SupplierTC.getResolver('findMany').extensions = {
39+
complexity: ({ args, childComplexity }) => childComplexity * (args.limit || 1000),
40+
};
41+
3242
SupplierTC.addRelation('productConnection', {
3343
resolver: () => ProductTC.getResolver('connection'),
3444
prepareArgs: {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* @flow */
2+
3+
// Read apollo-server docs
4+
// https://www.apollographql.com/docs/apollo-server/integrations/plugins/
5+
// Read graphql-query-complexity docs
6+
// https://github.com/slicknode/graphql-query-complexity
7+
8+
import type { ApolloServerPlugin } from 'apollo-server-plugin-base';
9+
import { getComplexity, simpleEstimator, fieldExtensionsEstimator } from 'graphql-query-complexity';
10+
import { separateOperations, type GraphQLSchema } from 'graphql';
11+
12+
export function initQueryComplexityPlugin(opts: { schema: GraphQLSchema, maxComplexity: number }) {
13+
return ({
14+
requestDidStart: () => {
15+
let complexity = 0;
16+
const maxComplexity = opts.maxComplexity || 1000;
17+
return {
18+
didResolveOperation({ request, document }) {
19+
/**
20+
* This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.
21+
* This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
22+
* More documentation can be found at https://github.com/ivome/graphql-query-complexity.
23+
*/
24+
complexity = getComplexity({
25+
// Our built schema
26+
schema: opts.schema,
27+
// To calculate query complexity properly,
28+
// we have to check if the document contains multiple operations
29+
// and eventually extract it operation from the whole query document.
30+
query: request.operationName
31+
? separateOperations(document)[request.operationName]
32+
: document,
33+
// The variables for our GraphQL query
34+
variables: request.variables,
35+
// Add any number of estimators. The estimators are invoked in order, the first
36+
// numeric value that is being returned by an estimator is used as the field complexity.
37+
// If no estimator returns a value, an exception is raised.
38+
estimators: [
39+
fieldExtensionsEstimator(),
40+
// Add more estimators here...
41+
// This will assign each field a complexity of 1
42+
// if no other estimator returned a value.
43+
simpleEstimator({ defaultComplexity: 1 }),
44+
],
45+
});
46+
// Here we can react to the calculated complexity,
47+
// like compare it with max and throw error when the threshold is reached.
48+
if (complexity >= maxComplexity) {
49+
throw new Error(
50+
`Sorry, too complicated query! ${complexity} is over ${maxComplexity} that is the max allowed complexity.`
51+
);
52+
}
53+
// And here we can e.g. subtract the complexity point from hourly API calls limit.
54+
if (request.operationName !== 'IntrospectionQuery') {
55+
console.log(
56+
`Used query ${request.operationName || ''} complexity points: ${complexity}`
57+
);
58+
}
59+
},
60+
willSendResponse({ response }) {
61+
response.extensions = response.extensions || {};
62+
response.extensions.complexity = complexity;
63+
response.extensions.maxComplexity = maxComplexity;
64+
},
65+
};
66+
},
67+
}: ApolloServerPlugin);
68+
}

0 commit comments

Comments
 (0)