Skip to content

Commit 23409fd

Browse files
committed
release: 1.1.1
1 parent 4414f46 commit 23409fd

16 files changed

+2165
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Changelog and release notes
22

3-
## Unreleased
3+
<!-- ## Unreleased -->
44
<!-- here goes all the unreleased changes descriptions -->
5+
6+
## v1.1.1
57
### Fixes
68
- fix crashing when of union's or interface type's `resolveType` function returns `undefined` or `null` (#731)
79
- fix crashing when no reflected type available for fields with params decorators (#724)

website/i18n/en.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,47 @@
461461
"version-1.1.0/version-1.1.0-validation": {
462462
"title": "Argument and Input validation",
463463
"sidebar_label": "Validation"
464+
},
465+
"version-1.1.1/version-1.1.1-complexity": {
466+
"title": "Query complexity"
467+
},
468+
"version-1.1.1/version-1.1.1-custom-decorators": {
469+
"title": "Custom decorators"
470+
},
471+
"version-1.1.1/version-1.1.1-dependency-injection": {
472+
"title": "Dependency injection"
473+
},
474+
"version-1.1.1/version-1.1.1-examples": {
475+
"title": "Examples",
476+
"sidebar_label": "List of examples"
477+
},
478+
"version-1.1.1/version-1.1.1-extensions": {
479+
"title": "Extensions"
480+
},
481+
"version-1.1.1/version-1.1.1-generic-types": {
482+
"title": "Generic Types"
483+
},
484+
"version-1.1.1/version-1.1.1-interfaces": {
485+
"title": "Interfaces"
486+
},
487+
"version-1.1.1/version-1.1.1-middlewares": {
488+
"title": "Middleware and guards"
489+
},
490+
"version-1.1.1/version-1.1.1-performance": {
491+
"title": "Performance"
492+
},
493+
"version-1.1.1/version-1.1.1-resolvers": {
494+
"title": "Resolvers"
495+
},
496+
"version-1.1.1/version-1.1.1-subscriptions": {
497+
"title": "Subscriptions"
498+
},
499+
"version-1.1.1/version-1.1.1-unions": {
500+
"title": "Unions"
501+
},
502+
"version-1.1.1/version-1.1.1-validation": {
503+
"title": "Argument and Input validation",
504+
"sidebar_label": "Validation"
464505
}
465506
},
466507
"links": {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
title: Query complexity
3+
id: version-1.1.1-complexity
4+
original_id: complexity
5+
---
6+
7+
A single GraphQL query can potentially generate a huge workload for a server, like thousands of database operations which can be used to cause DDoS attacks. In order to limit and keep track of what each GraphQL operation can do, `TypeGraphQL` provides the option of integrating with Query Complexity tools like [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).
8+
9+
This cost analysis-based solution is very promising, since we can define a “cost” per field and then analyze the AST to estimate the total cost of the GraphQL query. Of course all the analysis is handled by `graphql-query-complexity`.
10+
11+
All we must do is define our complexity cost for the fields, mutations or subscriptions in `TypeGraphQL` and implement `graphql-query-complexity` in whatever GraphQL server that is being used.
12+
13+
## How to use
14+
15+
First, we need to pass `complexity` as an option to the decorator on a field, query or mutation.
16+
17+
Example of complexity
18+
19+
```typescript
20+
@ObjectType()
21+
class MyObject {
22+
@Field({ complexity: 2 })
23+
publicField: string;
24+
25+
@Field({ complexity: ({ args, childComplexity }) => childComplexity + 1 })
26+
complexField: string;
27+
}
28+
```
29+
30+
The `complexity` option may be omitted if the complexity value is 1.
31+
Complexity can be passed as an option to any `@Field`, `@FieldResolver`, `@Mutation` or `@Subscription` decorator. If both `@FieldResolver` and `@Field` decorators of the same property have complexity defined, then the complexity passed to the field resolver decorator takes precedence.
32+
33+
In the next step, we will integrate `graphql-query-complexity` with the server that expose our GraphQL schema over HTTP.
34+
You can use it with `express-graphql` like [in the lib examples](https://github.com/slicknode/graphql-query-complexity/blob/b6a000c0984f7391f3b4e886e3df6a7ed1093b07/README.md#usage-with-express-graphql), however we will use Apollo Server like in our other examples:
35+
36+
```typescript
37+
async function bootstrap() {
38+
// ...build TypeGraphQL schema as always
39+
40+
// Create GraphQL server
41+
const server = new ApolloServer({
42+
schema,
43+
// Create a plugin that will allow for query complexity calculation for every request
44+
plugins: [
45+
{
46+
requestDidStart: () => ({
47+
didResolveOperation({ request, document }) {
48+
/**
49+
* This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.
50+
* This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
51+
* More documentation can be found at https://github.com/ivome/graphql-query-complexity.
52+
*/
53+
const complexity = getComplexity({
54+
// Our built schema
55+
schema,
56+
// To calculate query complexity properly,
57+
// we have to check only the requested operation
58+
// not the whole document that may contains multiple operations
59+
operationName: request.operationName,
60+
// The GraphQL query document
61+
query: document,
62+
// The variables for our GraphQL query
63+
variables: request.variables,
64+
// Add any number of estimators. The estimators are invoked in order, the first
65+
// numeric value that is being returned by an estimator is used as the field complexity.
66+
// If no estimator returns a value, an exception is raised.
67+
estimators: [
68+
// Using fieldExtensionsEstimator is mandatory to make it work with type-graphql.
69+
fieldExtensionsEstimator(),
70+
// Add more estimators here...
71+
// This will assign each field a complexity of 1
72+
// if no other estimator returned a value.
73+
simpleEstimator({ defaultComplexity: 1 }),
74+
],
75+
});
76+
// Here we can react to the calculated complexity,
77+
// like compare it with max and throw error when the threshold is reached.
78+
if (complexity > 20) {
79+
throw new Error(
80+
`Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`,
81+
);
82+
}
83+
// And here we can e.g. subtract the complexity point from hourly API calls limit.
84+
console.log("Used query complexity points:", complexity);
85+
},
86+
}),
87+
},
88+
],
89+
});
90+
91+
// ...start the server as always
92+
}
93+
```
94+
95+
And it's done! 😉
96+
97+
For more info about how query complexity is computed, please visit [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).
98+
99+
## Example
100+
101+
See how this works in the [simple query complexity example](https://github.com/MichalLytek/type-graphql/tree/v1.1.1/examples/query-complexity).
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: Custom decorators
3+
id: version-1.1.1-custom-decorators
4+
original_id: custom-decorators
5+
---
6+
7+
Custom decorators are a great way to reduce the boilerplate and reuse some common logic between different resolvers. TypeGraphQL supports two kinds of custom decorators - method and parameter.
8+
9+
## Method decorators
10+
11+
Using [middlewares](middlewares.md) allows to reuse some code between resolvers. To further reduce the boilerplate and have a nicer API, we can create our own custom method decorators.
12+
13+
They work in the same way as the [reusable middleware function](middlewares.md#reusable-middleware), however, in this case we need to call `createMethodDecorator` helper function with our middleware logic and return its value:
14+
15+
```typescript
16+
export function ValidateArgs(schema: JoiSchema) {
17+
return createMethodDecorator(async ({ args }, next) => {
18+
// here place your middleware code that uses custom decorator arguments
19+
20+
// e.g. validation logic based on schema using joi
21+
await joiValidate(schema, args);
22+
return next();
23+
});
24+
}
25+
```
26+
27+
The usage is then very simple, as we have a custom, descriptive decorator - we just place it above the resolver/field and pass the required arguments to it:
28+
29+
```typescript
30+
@Resolver()
31+
export class RecipeResolver {
32+
@ValidateArgs(MyArgsSchema) // custom decorator
33+
@UseMiddleware(ResolveTime) // explicit middleware
34+
@Query()
35+
randomValue(@Args() { scale }: MyArgs): number {
36+
return Math.random() * scale;
37+
}
38+
}
39+
```
40+
41+
## Parameter decorators
42+
43+
Parameter decorators are just like the custom method decorators or middlewares but with an ability to return some value that will be injected to the method as a parameter. Thanks to this, it reduces the pollution in `context` which was used as a workaround for the communication between reusable middlewares and resolvers.
44+
45+
They might be just a simple data extractor function, that makes our resolver more unit test friendly:
46+
47+
```typescript
48+
function CurrentUser() {
49+
return createParamDecorator<MyContextType>(({ context }) => context.currentUser);
50+
}
51+
```
52+
53+
Or might be a more advanced one that performs some calculations and encapsulates some logic. Compared to middlewares, they allows for a more granular control on executing the code, like calculating fields map based on GraphQL info only when it's really needed (requested by using the `@Fields()` decorator):
54+
55+
```typescript
56+
function Fields(level = 1): ParameterDecorator {
57+
return createParamDecorator(({ info }) => {
58+
const fieldsMap: FieldsMap = {};
59+
// calculate an object with info about requested fields
60+
// based on GraphQL `info` parameter of the resolver and the level parameter
61+
return fieldsMap;
62+
});
63+
}
64+
```
65+
66+
Then we can use our custom param decorators in the resolvers just like the built-in decorators:
67+
68+
```typescript
69+
@Resolver()
70+
export class RecipeResolver {
71+
constructor(private readonly recipesRepository: Repository<Recipe>) {}
72+
73+
@Authorized()
74+
@Mutation(returns => Recipe)
75+
async addRecipe(
76+
@Args() recipeData: AddRecipeInput,
77+
// here we place our custom decorator
78+
// just like the built-in one
79+
@CurrentUser() currentUser: User,
80+
) {
81+
const recipe: Recipe = {
82+
...recipeData,
83+
// and use the data returned from custom decorator in our resolver code
84+
author: currentUser,
85+
};
86+
await this.recipesRepository.save(recipe);
87+
return recipe;
88+
}
89+
90+
@Query(returns => Recipe, { nullable: true })
91+
async recipe(
92+
@Arg("id") id: string,
93+
// our custom decorator that parses the fields from graphql query info
94+
@Fields() fields: FieldsMap,
95+
) {
96+
return await this.recipesRepository.find(id, {
97+
// use the fields map as a select projection to optimize db queries
98+
select: fields,
99+
});
100+
}
101+
}
102+
```
103+
104+
## Example
105+
106+
See how different kinds of custom decorators work in the [custom decorators and middlewares example](https://github.com/MichalLytek/type-graphql/tree/v1.1.1/examples/middlewares-custom-decorators).

0 commit comments

Comments
 (0)