Skip to content

Commit

Permalink
docs(decorators): add info about method and param decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalLytek committed May 5, 2019
1 parent 59e9e3a commit 7e260de
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 23 deletions.
104 changes: 104 additions & 0 deletions docs/custom-decorators.md
@@ -0,0 +1,104 @@
---
title: 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 the id:

```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/19majkel94/type-graphql/tree/master/examples/middlewares-custom-decorators).
26 changes: 3 additions & 23 deletions docs/middlewares.md
Expand Up @@ -180,28 +180,8 @@ const schema = await buildSchema({

### Custom Decorators

If we want to have a more descriptive and declarative API, we can also create custom decorators. They work in the same way as the reusable middleware function, however, in this case we need to return the `UseMiddleware` decorator function:
If we want to use middlewares with a more descriptive and declarative API, we can also create a custom method decorators. See how to do this in [custom decorators docs](custom-decorators.md#method-decorators).

```typescript
export function ValidateArgs<T extends object>(schema: Schema<T>) {
return UseMiddleware(async ({ args }, next) => {
// here place your validation logic, e.g. 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 the id:
## Example

```typescript
@Resolver()
export class RecipeResolver {
@Query()
@ValidateArgs(MyArgsSchema) // custom decorator
@UseMiddleware(ResolveTime) // explicit middleware
randomValue(@Args() { scale }: MyArgs): number {
return Math.random() * scale;
}
}
```
See how different kinds of middlewares work in the [middlewares and custom decorators example](https://github.com/19majkel94/type-graphql/tree/master/examples/middlewares-custom-decorators).
3 changes: 3 additions & 0 deletions website/i18n/en.json
Expand Up @@ -17,6 +17,9 @@
"complexity": {
"title": "Query complexity"
},
"custom-decorators": {
"title": "Custom decorators"
},
"dependency-injection": {
"title": "Dependency injection"
},
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.json
Expand Up @@ -16,6 +16,7 @@
"inheritance",
"generic-types",
"middlewares",
"custom-decorators",
"complexity"
],
"Others": ["emit-schema", "browser-usage"]
Expand Down

0 comments on commit 7e260de

Please sign in to comment.