Skip to content

Commit

Permalink
release: 1.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalLytek committed Nov 4, 2020
1 parent 4414f46 commit 23409fd
Show file tree
Hide file tree
Showing 16 changed files with 2,165 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Changelog and release notes

## Unreleased
<!-- ## Unreleased -->
<!-- here goes all the unreleased changes descriptions -->

## v1.1.1
### Fixes
- fix crashing when of union's or interface type's `resolveType` function returns `undefined` or `null` (#731)
- fix crashing when no reflected type available for fields with params decorators (#724)
Expand Down
41 changes: 41 additions & 0 deletions website/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,47 @@
"version-1.1.0/version-1.1.0-validation": {
"title": "Argument and Input validation",
"sidebar_label": "Validation"
},
"version-1.1.1/version-1.1.1-complexity": {
"title": "Query complexity"
},
"version-1.1.1/version-1.1.1-custom-decorators": {
"title": "Custom decorators"
},
"version-1.1.1/version-1.1.1-dependency-injection": {
"title": "Dependency injection"
},
"version-1.1.1/version-1.1.1-examples": {
"title": "Examples",
"sidebar_label": "List of examples"
},
"version-1.1.1/version-1.1.1-extensions": {
"title": "Extensions"
},
"version-1.1.1/version-1.1.1-generic-types": {
"title": "Generic Types"
},
"version-1.1.1/version-1.1.1-interfaces": {
"title": "Interfaces"
},
"version-1.1.1/version-1.1.1-middlewares": {
"title": "Middleware and guards"
},
"version-1.1.1/version-1.1.1-performance": {
"title": "Performance"
},
"version-1.1.1/version-1.1.1-resolvers": {
"title": "Resolvers"
},
"version-1.1.1/version-1.1.1-subscriptions": {
"title": "Subscriptions"
},
"version-1.1.1/version-1.1.1-unions": {
"title": "Unions"
},
"version-1.1.1/version-1.1.1-validation": {
"title": "Argument and Input validation",
"sidebar_label": "Validation"
}
},
"links": {
Expand Down
101 changes: 101 additions & 0 deletions website/versioned_docs/version-1.1.1/complexity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Query complexity
id: version-1.1.1-complexity
original_id: complexity
---

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).

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`.

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.

## How to use

First, we need to pass `complexity` as an option to the decorator on a field, query or mutation.

Example of complexity

```typescript
@ObjectType()
class MyObject {
@Field({ complexity: 2 })
publicField: string;

@Field({ complexity: ({ args, childComplexity }) => childComplexity + 1 })
complexField: string;
}
```

The `complexity` option may be omitted if the complexity value is 1.
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.

In the next step, we will integrate `graphql-query-complexity` with the server that expose our GraphQL schema over HTTP.
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:

```typescript
async function bootstrap() {
// ...build TypeGraphQL schema as always

// Create GraphQL server
const server = new ApolloServer({
schema,
// Create a plugin that will allow for query complexity calculation for every request
plugins: [
{
requestDidStart: () => ({
didResolveOperation({ request, document }) {
/**
* This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.
* This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
* More documentation can be found at https://github.com/ivome/graphql-query-complexity.
*/
const complexity = getComplexity({
// Our built schema
schema,
// To calculate query complexity properly,
// we have to check only the requested operation
// not the whole document that may contains multiple operations
operationName: request.operationName,
// The GraphQL query document
query: document,
// The variables for our GraphQL query
variables: request.variables,
// Add any number of estimators. The estimators are invoked in order, the first
// numeric value that is being returned by an estimator is used as the field complexity.
// If no estimator returns a value, an exception is raised.
estimators: [
// Using fieldExtensionsEstimator is mandatory to make it work with type-graphql.
fieldExtensionsEstimator(),
// Add more estimators here...
// This will assign each field a complexity of 1
// if no other estimator returned a value.
simpleEstimator({ defaultComplexity: 1 }),
],
});
// Here we can react to the calculated complexity,
// like compare it with max and throw error when the threshold is reached.
if (complexity > 20) {
throw new Error(
`Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`,
);
}
// And here we can e.g. subtract the complexity point from hourly API calls limit.
console.log("Used query complexity points:", complexity);
},
}),
},
],
});

// ...start the server as always
}
```

And it's done! 😉

For more info about how query complexity is computed, please visit [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).

## Example

See how this works in the [simple query complexity example](https://github.com/MichalLytek/type-graphql/tree/v1.1.1/examples/query-complexity).
106 changes: 106 additions & 0 deletions website/versioned_docs/version-1.1.1/custom-decorators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: Custom decorators
id: version-1.1.1-custom-decorators
original_id: custom-decorators
---

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.

## Method decorators

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.

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:

```typescript
export function ValidateArgs(schema: JoiSchema) {
return createMethodDecorator(async ({ args }, next) => {
// here place your middleware code that uses custom decorator arguments

// e.g. validation logic based on schema using joi
await joiValidate(schema, args);
return next();
});
}
```

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:

```typescript
@Resolver()
export class RecipeResolver {
@ValidateArgs(MyArgsSchema) // custom decorator
@UseMiddleware(ResolveTime) // explicit middleware
@Query()
randomValue(@Args() { scale }: MyArgs): number {
return Math.random() * scale;
}
}
```

## Parameter decorators

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.

They might be just a simple data extractor function, that makes our resolver more unit test friendly:

```typescript
function CurrentUser() {
return createParamDecorator<MyContextType>(({ context }) => context.currentUser);
}
```

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):

```typescript
function Fields(level = 1): ParameterDecorator {
return createParamDecorator(({ info }) => {
const fieldsMap: FieldsMap = {};
// calculate an object with info about requested fields
// based on GraphQL `info` parameter of the resolver and the level parameter
return fieldsMap;
});
}
```

Then we can use our custom param decorators in the resolvers just like the built-in decorators:

```typescript
@Resolver()
export class RecipeResolver {
constructor(private readonly recipesRepository: Repository<Recipe>) {}

@Authorized()
@Mutation(returns => Recipe)
async addRecipe(
@Args() recipeData: AddRecipeInput,
// here we place our custom decorator
// just like the built-in one
@CurrentUser() currentUser: User,
) {
const recipe: Recipe = {
...recipeData,
// and use the data returned from custom decorator in our resolver code
author: currentUser,
};
await this.recipesRepository.save(recipe);
return recipe;
}

@Query(returns => Recipe, { nullable: true })
async recipe(
@Arg("id") id: string,
// our custom decorator that parses the fields from graphql query info
@Fields() fields: FieldsMap,
) {
return await this.recipesRepository.find(id, {
// use the fields map as a select projection to optimize db queries
select: fields,
});
}
}
```

## Example

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).
Loading

0 comments on commit 23409fd

Please sign in to comment.