-
-
Notifications
You must be signed in to change notification settings - Fork 676
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #329 from 19majkel94/custom-param-decorators
Custom parameter decorators
- Loading branch information
Showing
31 changed files
with
265 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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/19majkel94/type-graphql/tree/master/examples/middlewares-custom-decorators). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import User from "./user"; | ||
|
||
export interface Context { | ||
currentUser: User; | ||
} |
6 changes: 6 additions & 0 deletions
6
examples/middlewares-custom-decorators/decorators/current-user.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createParamDecorator } from "../../../src"; | ||
import { Context } from "../context"; | ||
|
||
export default function CurrentUser() { | ||
return createParamDecorator<Context>(({ context }) => context.currentUser); | ||
} |
4 changes: 2 additions & 2 deletions
4
...s/middlewares/decorators/validate-args.ts → ...om-decorators/decorators/validate-args.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default interface User { | ||
id: number; | ||
name: string; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { UseMiddleware } from "./UseMiddleware"; | ||
import { MiddlewareFn } from "../interfaces/Middleware"; | ||
|
||
export function createMethodDecorator<TContextType = {}>( | ||
resolver: MiddlewareFn<TContextType>, | ||
): MethodDecorator { | ||
return UseMiddleware(resolver); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ResolverData } from "../interfaces"; | ||
import { getMetadataStorage } from "../metadata/getMetadataStorage"; | ||
import { SymbolKeysNotSupportedError } from "../errors"; | ||
|
||
export function createParamDecorator<TContextType = {}>( | ||
resolver: (resolverData: ResolverData<TContextType>) => any, | ||
): ParameterDecorator { | ||
return (prototype, propertyKey, parameterIndex) => { | ||
if (typeof propertyKey === "symbol") { | ||
throw new SymbolKeysNotSupportedError(); | ||
} | ||
getMetadataStorage().collectHandlerParamMetadata({ | ||
kind: "custom", | ||
target: prototype.constructor, | ||
methodName: propertyKey, | ||
index: parameterIndex, | ||
resolver, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.