Skip to content

Commit 2a39116

Browse files
committed
release: 0.17.5
1 parent b7d2e6b commit 2a39116

16 files changed

+1377
-3
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+
## v0.17.5
57
### Features
68
- rename `DepreciationOptions` interface to `DeprecationOptions` and deprecate the old one
79
- update deps to newest minor versions (`tslib`, `semver`, `graphql-query-complexity` and `glob`)

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "type-graphql",
3-
"version": "0.17.4",
3+
"version": "0.17.5",
44
"author": {
55
"name": "Michał Lytek",
66
"url": "https://github.com/19majkel94"

website/i18n/en.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,40 @@
316316
},
317317
"version-0.17.4/version-0.17.4-unions": {
318318
"title": "Unions"
319+
},
320+
"version-0.17.5/version-0.17.5-complexity": {
321+
"title": "Query complexity"
322+
},
323+
"version-0.17.5/version-0.17.5-custom-decorators": {
324+
"title": "Custom decorators"
325+
},
326+
"version-0.17.5/version-0.17.5-dependency-injection": {
327+
"title": "Dependency injection"
328+
},
329+
"version-0.17.5/version-0.17.5-examples": {
330+
"title": "Examples",
331+
"sidebar_label": "List of examples"
332+
},
333+
"version-0.17.5/version-0.17.5-faq": {
334+
"title": "Frequently Asked Questions"
335+
},
336+
"version-0.17.5/version-0.17.5-generic-types": {
337+
"title": "Generic Types"
338+
},
339+
"version-0.17.5/version-0.17.5-interfaces": {
340+
"title": "Interfaces"
341+
},
342+
"version-0.17.5/version-0.17.5-middlewares": {
343+
"title": "Middleware and guards"
344+
},
345+
"version-0.17.5/version-0.17.5-subscriptions": {
346+
"title": "Subscriptions"
347+
},
348+
"version-0.17.5/version-0.17.5-types-and-fields": {
349+
"title": "Types and Fields"
350+
},
351+
"version-0.17.5/version-0.17.5-unions": {
352+
"title": "Unions"
319353
}
320354
},
321355
"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-0.17.5-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 if the document contains multiple operations
58+
// and eventually extract it operation from the whole query document.
59+
query: request.operationName
60+
? separateOperations(document)[request.operationName]
61+
: 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 fieldConfigEstimator is mandatory to make it work with type-graphql.
69+
fieldConfigEstimator(),
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/19majkel94/type-graphql/tree/v0.17.5/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-0.17.5-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/19majkel94/type-graphql/tree/v0.17.5/examples/middlewares-custom-decorators).

0 commit comments

Comments
 (0)