Extensions

warning Warning This chapter applies only to the code first approach.

Extensions is an advanced, low-level feature that lets you define arbitrary data in the types configuration. Attaching custom metadata to certain fields allows you to create more sophisticated, generic solutions. For example, with extensions, you can define field-level roles required to access particular fields. Such roles can be reflected at runtime to determine whether the caller has sufficient permissions to retrieve a specific field.

Adding custom metadata#

To attach custom metadata for a field, use the @Extensions() decorator exported from the @nestjs/graphql package.

@Field()
@Extensions({ role: Role.ADMIN })
password: string;

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.

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

Using custom metadata#

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 that matches roles required to retrieve a field with the caller permissions (field-level permissions system).

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:

export const checkRoleMiddleware: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
const { info } = ctx;
const { extensions } = info.parentType.getFields()[info.fieldName];
/**
* In a real-world application, the "userRole" variable
* should represent the caller's (user) role (for example, "ctx.user.role").
*/
const userRole = Role.USER;
if (userRole === extensions.role) {
// or just "return null" to ignore
throw new ForbiddenException(
`User does not have sufficient permissions to access "${info.fieldName}" field.`,
);
}
return next();
};

With this in place, we can register a middleware for the password field, as follows:

@Field({ middleware: [checkRoleMiddleware] })
@Extensions({ role: Role.ADMIN })
password: string;