You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/graphql/extensions.md
+28-36Lines changed: 28 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,51 +14,43 @@ To attach custom metadata for a field, use the `@Extensions()` decorator exporte
14
14
password: string;
15
15
```
16
16
17
-
In the example above, we assigned the `role` metadata property the value of `Role.ADMIN`. `Role` is a simple TypeScript enum that groups all the user roles available in our system.
17
+
In the example above, we assigned the `role` metadata property the value of `Role.ADMIN`. `Role` is a simple TypeScript enum that groups all the user roles available in our system.
18
18
19
19
Note, in addition to setting metadata on fields, you can use the `@Extensions()` decorator at the class level and method level (e.g., on the query handler).
20
20
21
21
#### Using custom metadata
22
22
23
-
The logic that leverages the custom metadata can be as complex as needed. For example, you can create a simple interceptor that stores/logs events per method invocation, or create a sophisticated guard that **analyzes requested fields**, iterates through the `GraphQLObjectType` definition, and matches the roles required to retrieve specific fields with the caller permissions (field-level permissions system).
23
+
Logic that leverages the custom metadata can be as complex as needed. For example, you can create a simple interceptor that stores/logs events per method invocation, or a [field middleware](/graphql/field-middleware) that matches roles required to retrieve a field with the caller permissions (field-level permissions system).
24
24
25
-
Let's define a `FieldRolesGuard` that implements a basic version of such a field-level permissions system.
25
+
For illustration purposes, let's define a `checkRoleMiddleware` that compares a user's role (hardcoded here) with a role required to access a target field:
* In a real-world application, the "userRole" variable
37
+
* should represent the caller's (user) role (for example, "ctx.user.role").
38
+
*/
39
+
const userRole =Role.USER;
40
+
if (userRole===extensions.role) {
41
+
// or just "return null" to ignore
42
+
thrownewForbiddenException(
43
+
`User does not have sufficient permissions to access "${info.fieldName}" field.`,
44
+
);
56
45
}
57
-
}
46
+
returnnext();
47
+
};
58
48
```
59
49
60
-
> warning **Warning** For illustration purposes, we assumed that **every** resolver returns either the `GraphQLObjectType` or `GraphQLNonNull` that wraps the object type. In a real-world application, you should cover other cases (scalars, etc.). Note that using this particular implementation can lead to unexpected errors (e.g., missing `getFields()` method).
61
-
62
-
In the example above, we've used the [graphql-fields](https://github.com/robrichard/graphql-fields) package that turns the `GraphQLResolveInfo` object into an object that consists of the requested fields. We used this specific library to make the presented example somewhat simpler.
50
+
With this in place, we can register a middleware for the `password` field, as follows:
63
51
64
-
With this guard in place, if the return type of any resolver contains a field annotated with the `@Extensions({{ '{' }} role: Role.ADMIN {{ '}' }}})` decorator, this `role` (`Role.ADMIN`) will be logged in the console **if requested** in the GraphQL query.
> warning **Warning** This chapter applies only to the code first approach.
4
+
5
+
Field Middleware lets you run arbitrary code **before or after** a field is resolved. A field middleware can be used to convert the result of a field, validate the arguments of a field, or even check field-level roles (for example, required to access a target field for which a middleware function is executed).
6
+
7
+
You can connect multiple middleware functions to a field. In this case, they will be called sequentially along the chain where the previous middleware decides to call the next one. The order of the middleware functions in the `middleware` array is important. The first resolver is the "most-outer" layer, so it gets executed first and last (similarly to the `graphql-middleware` package). The second resolver is the "second-outer" layer, so it gets executed second and second to last.
8
+
9
+
#### Getting started
10
+
11
+
Let's start off by creating a simple middleware that will log a field value before it's sent back to the client:
> info **Hint** The `MiddlewareContext` is an object that consist of the same arguments that are normally received by the GraphQL resolver function (`{{ '{' }} source, args, context, info {{ '}' }}`), while `NextFn` is a function that let you execute the next middleware in the stack (bound to this field) or the actual field resolver.
27
+
28
+
> warning **Warning** Field middleware functions cannot inject dependencies nor access Nest's DI container as they are designed to be very lightweight and shouldn't perform any potentially time-consuming operations (like retrieving data from the database). If you need to call external services/query data from the data source, you should do it in a guard/interceptor bounded to a root query/mutation handler and assing it to `context` object which you can access from within the field middleware (specifically, from the `MiddlewareContext` object).
29
+
30
+
Note that field middleware must match the `FieldMiddleware` interface. In the example above, we first run the `next()` function (which executes the actual field resolver and returns a field value) and then, we log this value to our terminal. Also, the value returned from the middleware function completely overrides the previous value and since we don't want to perform any changes, we simply return the original value.
31
+
32
+
With this in place, we can register our middleware directly in the `@Field()` decorator, as follows:
33
+
34
+
```typescript
35
+
@ObjectType()
36
+
exportclassRecipe {
37
+
@Field({ middleware: [loggerMiddleware] })
38
+
title:string;
39
+
}
40
+
```
41
+
42
+
Now whenever we request the `title` field of `Recipe` object type, the original field's value will be logged to the console.
43
+
44
+
> info **Hint** To learn how you can implement a field-level permissions system with the use of [extensions](/graphql/extensions) feature, check out this [section](/graphql/extensions#using-custom-metadata).
45
+
46
+
Also, as mentioned above, we can control the field's value from within the middleware function. For demonstration purposes, let's capitalise a recipe's title (if present):
47
+
48
+
```typescript
49
+
const value =awaitnext();
50
+
returnvalue?.toUpperCase();
51
+
```
52
+
53
+
In this case, every title will be automatically uppercased, when requested.
54
+
55
+
Likewise, you can bind a field middleware to a custom field resolver (a method annotated with the `@ResolveField()` decorator), as follows:
> warning **Warning** In case enhancers are enabled at the field resolver level ([read more](/graphl//other-features#execute-enhancers-at-the-field-resolver-level)), field middleware functions will run before any interceptors, guards, etc., **bounded to the method** (but after the root-level enhancers registered for query or mutation handlers).
65
+
66
+
#### Global field middleware
67
+
68
+
In addition to binding a middleware directly to a specific field, you can also register one or multiple middleware functions globally. In this case, they will be automatically connected to all fields of your object types.
69
+
70
+
```typescript
71
+
GraphQLModule.forRoot({
72
+
autoSchemaFile: 'schema.gql',
73
+
buildSchemaOptions: {
74
+
fieldMiddleware: [loggerMiddleware],
75
+
},
76
+
}),
77
+
```
78
+
79
+
> info **Hint** Globally registered field middleware functions will be executed **before** locally registered ones (those bound directly to specific fields).
0 commit comments