Graphql: Add a @DirectiveResolver decorator

Created on 13 May 2018  路  12Comments  路  Source: nestjs/graphql

Currently, NestJS doesn't support GraphQL directives at all, since the directiveResolvers in makeExecutableSchema is missing https://github.com/nestjs/graphql/blob/master/lib/graphql.factory.ts#L28

I wanted to use directives in my model (for authentication etc) and I bumped into https://github.com/nestjs/graphql/issues/17 but I realized it's not just the mapping to Guards, but that NestJS is totally missing directives.

PS: An example of how to use Directives https://codeburst.io/use-custom-directives-to-protect-your-graphql-apis-a78cbbe17355 and how graphql-tools implements it here https://github.com/apollographql/graphql-tools/blob/master/docs/source/directive-resolvers.md#directive-example

todo feature

Most helpful comment

I made some PoC on how to use dependency injection in Directive classes, it requires one ugly hack but looks slick.
graphql.module.ts

@Module({
    imports: [
        GraphQLModule.forRootAsync({
            useFactory(factory: DirectivesFactory) {
                return {
                    typePaths: ['./**/*.graphql'],
                    debug: true,
                    playground: true,
                    installSubscriptionHandlers: true,
                    definitions: {
                        path: join(process.cwd(), 'src/graphql.schema.ts'),
                        outputAs: 'class',
                    },
                    transformSchema: (schema: GraphQLSchema) => {
                        return mergeSchemas({schemas: [schema], schemaDirectives: factory.register() });
                    }
                };
            },
            imports: [DirectivesModule],
            inject: [DirectivesFactory]
        }),

directives.module.ts

import {HttpModule, Module} from '@nestjs/common';
import {DirectivesFactory} from "./directives.factory";

@Module({
    imports: [HttpModule],
    providers: [ DirectivesFactory],
    exports: [ DirectivesFactory ]
})
export class DirectivesModule {}

directives.factory.ts

import {Inject, Injectable} from "@nestjs/common";
import {SchemaDirectiveVisitor} from "graphql-tools";
import {RestDirective} from "./rest-directive";

@Injectable()
export class DirectivesFactory {

    @Inject()
    restDirective: RestDirective;

    register() {
        return {
            rest: this.restDirective.build()
        }
    }
}

And last one rest-directive.ts

import {HttpService, Inject, Injectable} from "@nestjs/common";
import {SchemaDirectiveVisitor} from "graphql-tools";

type DirectiveArguments = {
    [name: string]: any;
}

@Injectable()
export class RestDirective {
    @Inject()
    private http: HttpService;

    async visitFieldDefinition(args: DirectiveArguments){
        const { url } = args;
        const response = await this.http.get(url).toPromise();
        return response.data;
    }

    build(): typeof SchemaDirectiveVisitor {
        const rest = this;
        class Directive extends SchemaDirectiveVisitor {
            public visitFieldDefinition(field) {
                field.resolve = async () => await rest.visitFieldDefinition(this.args);
            }
        }

        return Directive
    }
}

All 12 comments

The more I read into graphql (I'm still a newbie), the more I think NestJs should embrace SchemaDirectives instead / also :-)

Any updates on this issue?

New 5.0.0 has been released.

Nevertheless, we didn't ship @DirectiveResolver() decorator. GraphQLModule is compatible with directives though. The reason for this is that Apollo forces us to pass types, instead of the concrete instances of the resolvers. Therefore, directive resolvers won't be able to access the injector and internal Nest module context because we can't register them as providers.

To register directive resolver, simply pass directiveResolvers array to the GraphQLModule.forRoot() method.

Thanks @kamilmysliwiec - can you provide a simplistic working example?\
I think it will be useful for any future reference as well.

PS: I made it work as PoC back with NestJs 4.6 & @nestjs/graphql 2.0.0 with this hack:

configure() {
   const self = this;
    // @note: we need an unbound function here, not a () =>
    this.graphQLFactory.createSchema = function(
      schemaDefintion = { typeDefs: [] },
    ) {
      return makeExecutableSchema({
        ...schemaDefintion,
        directiveResolvers: self.directiveResolvers,
        resolvers: {
          ...this.resolversExplorerService.explore(), // ignore private error, its a hack anyway!
          ...(schemaDefintion.resolvers || {}),
        },
      });
    };

    const schema = this.graphQLFactory.createSchema({ typeDefs });
   ....
}

@kamilmysliwiec I am also interested in an example how to add directive support "the intended way".

I made some PoC on how to use dependency injection in Directive classes, it requires one ugly hack but looks slick.
graphql.module.ts

@Module({
    imports: [
        GraphQLModule.forRootAsync({
            useFactory(factory: DirectivesFactory) {
                return {
                    typePaths: ['./**/*.graphql'],
                    debug: true,
                    playground: true,
                    installSubscriptionHandlers: true,
                    definitions: {
                        path: join(process.cwd(), 'src/graphql.schema.ts'),
                        outputAs: 'class',
                    },
                    transformSchema: (schema: GraphQLSchema) => {
                        return mergeSchemas({schemas: [schema], schemaDirectives: factory.register() });
                    }
                };
            },
            imports: [DirectivesModule],
            inject: [DirectivesFactory]
        }),

directives.module.ts

import {HttpModule, Module} from '@nestjs/common';
import {DirectivesFactory} from "./directives.factory";

@Module({
    imports: [HttpModule],
    providers: [ DirectivesFactory],
    exports: [ DirectivesFactory ]
})
export class DirectivesModule {}

directives.factory.ts

import {Inject, Injectable} from "@nestjs/common";
import {SchemaDirectiveVisitor} from "graphql-tools";
import {RestDirective} from "./rest-directive";

@Injectable()
export class DirectivesFactory {

    @Inject()
    restDirective: RestDirective;

    register() {
        return {
            rest: this.restDirective.build()
        }
    }
}

And last one rest-directive.ts

import {HttpService, Inject, Injectable} from "@nestjs/common";
import {SchemaDirectiveVisitor} from "graphql-tools";

type DirectiveArguments = {
    [name: string]: any;
}

@Injectable()
export class RestDirective {
    @Inject()
    private http: HttpService;

    async visitFieldDefinition(args: DirectiveArguments){
        const { url } = args;
        const response = await this.http.get(url).toPromise();
        return response.data;
    }

    build(): typeof SchemaDirectiveVisitor {
        const rest = this;
        class Directive extends SchemaDirectiveVisitor {
            public visitFieldDefinition(field) {
                field.resolve = async () => await rest.visitFieldDefinition(this.args);
            }
        }

        return Directive
    }
}

Then you use can use it like this from schema:

directive @rest(url: String) on FIELD_DEFINITION

# the schema allows the following query:
type Query {
    getWeather: WeatherResponse @rest(url: "https://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b6907d289e10d714a6e88b30761fae22")
}

Any update on the intended way to do this? We would simply need the default @skip and @include directives.

@sebastian-schlecht check out couple of comments above, I posted some workaround to this

Is there any workaround how I can use directives in a code-first approach? type-graphql introduced a @Directive decorator, but I cannot use it with NestJS :(

@lookapanda I have open a ticket for that problem. Hope anyone can add the new version from type-graphql:

https://github.com/nestjs/graphql/issues/618

@Ponjimon
The @Directive() decorator is exported from the @nestjs/graphql package.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fenos picture fenos  路  5Comments

cschroeter picture cschroeter  路  3Comments

galkin picture galkin  路  4Comments

vnenkpet picture vnenkpet  路  3Comments

Warchant picture Warchant  路  4Comments