From dcb62c2d50348b58e3ba9349346a9c56ee47212b Mon Sep 17 00:00:00 2001 From: Giau Tran Minh Date: Thu, 1 Feb 2018 09:20:26 +0700 Subject: [PATCH 1/6] Added directiveResolvers as prop --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 52ba3878cf..bf0ee00e01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,7 +52,7 @@ export class GraphQLServer { if (props.schema) { this.executableSchema = props.schema } else if (props.typeDefs && props.resolvers) { - let { typeDefs, resolvers } = props + let { directiveResolvers, typeDefs, resolvers } = props // read from .graphql file if path provided if (typeDefs.endsWith('graphql')) { @@ -69,6 +69,7 @@ export class GraphQLServer { ? { Upload: GraphQLUpload } : {} this.executableSchema = makeExecutableSchema({ + directiveResolvers, typeDefs, resolvers: { ...uploadMixin, From 88a3b7136146e4374dcaf7e92641ce6f4d9f588f Mon Sep 17 00:00:00 2001 From: Giau Tran Minh Date: Thu, 1 Feb 2018 09:22:32 +0700 Subject: [PATCH 2/6] Added type for directiveResolvers --- src/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types.ts b/src/types.ts index aeab067d62..442737ccdf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,7 @@ import { GraphQLTypeResolver, ValidationContext, } from 'graphql' +import { IDirectiveResolvers } from 'graphql-tools' import { SubscriptionOptions } from 'graphql-subscriptions/dist/subscriptions-manager' import { LogFunction } from 'apollo-server-core' @@ -68,6 +69,7 @@ export interface Options extends ApolloServerOptions { } export interface Props { + directiveResolvers?: IDirectiveResolvers typeDefs?: string resolvers?: IResolvers schema?: GraphQLSchema From ce487a28708bfd5524b61bbcdd52258f254993fd Mon Sep 17 00:00:00 2001 From: Giau Tran Minh Date: Thu, 1 Feb 2018 09:23:40 +0700 Subject: [PATCH 3/6] Added type for directiveResolvers --- src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.ts b/src/types.ts index 442737ccdf..0bba9941ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,6 +77,7 @@ export interface Props { } export interface LambdaProps { + directiveResolvers?: IDirectiveResolvers typeDefs?: string resolvers?: IResolvers schema?: GraphQLSchema From 3bc99289e9c9abfa1b3d4691f8de06f1d7cbdb42 Mon Sep 17 00:00:00 2001 From: Giau Tran Minh Date: Thu, 1 Feb 2018 09:24:30 +0700 Subject: [PATCH 4/6] Added directiveResolvers for lambda --- src/lambda.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lambda.ts b/src/lambda.ts index 5fe2ef72ec..57b2ce53f5 100644 --- a/src/lambda.ts +++ b/src/lambda.ts @@ -26,7 +26,7 @@ export class GraphQLServerLambda { if (props.schema) { this.executableSchema = props.schema } else if (props.typeDefs && props.resolvers) { - let { typeDefs, resolvers } = props + let { directiveResolvers, typeDefs, resolvers } = props // read from .graphql file if path provided if (typeDefs.endsWith('graphql')) { @@ -42,6 +42,7 @@ export class GraphQLServerLambda { } this.executableSchema = makeExecutableSchema({ + directiveResolvers, typeDefs, resolvers, }) From c98d7d7929d96742b96fdc82727e33bd7efd4d31 Mon Sep 17 00:00:00 2001 From: fabien0102 Date: Wed, 14 Feb 2018 08:13:02 +0100 Subject: [PATCH 5/6] Fix custom directive type --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 0bba9941ff..9cb03c798c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,7 +8,7 @@ import { GraphQLTypeResolver, ValidationContext, } from 'graphql' -import { IDirectiveResolvers } from 'graphql-tools' +import { IDirectiveResolvers } from 'graphql-tools/dist/Interfaces' import { SubscriptionOptions } from 'graphql-subscriptions/dist/subscriptions-manager' import { LogFunction } from 'apollo-server-core' From 71dbb6f17ef21b605a9ca707cf5a10f1b65126cd Mon Sep 17 00:00:00 2001 From: fabien0102 Date: Wed, 14 Feb 2018 08:25:19 +0100 Subject: [PATCH 6/6] Add an example from custom directives implementation --- examples/custom-directives/README.md | 95 +++++++++++++++++++++++++ examples/custom-directives/index.js | 48 +++++++++++++ examples/custom-directives/package.json | 8 +++ 3 files changed, 151 insertions(+) create mode 100644 examples/custom-directives/README.md create mode 100644 examples/custom-directives/index.js create mode 100644 examples/custom-directives/package.json diff --git a/examples/custom-directives/README.md b/examples/custom-directives/README.md new file mode 100644 index 0000000000..c755c370bc --- /dev/null +++ b/examples/custom-directives/README.md @@ -0,0 +1,95 @@ +# custom-directive + +This directory contains a simple GraphQL with custom directives example based on `graphql-yoga`. + +## Get started + +**Clone the repository:** + +```sh +git clone https://github.com/graphcool/graphql-yoga/ +cd graphql-yoga/examples/custom-directives +``` + +**Install dependencies and run the app:** + +```sh +yarn install # or npm install +yarn start # or npm start +``` + +## Testing + +Open your browser at [http://localhost:4000](http://localhost:4000) and start sending queries. + +**Query `hello`:** + +```graphql +query { + hello +} +``` + +The server returns the following response: + +```json +{ + "data": { + "hello": "HELLO WORD" + } +} +``` + +Note that the original `Hello Word` output from the resolver is now in upper case due to our custom `@upper` directive. + +**Query `secret` (with role set as `admin`):** +```graphql +query { + secret +} +``` + +The server returns the following response: + +```json +{ + "data": { + "secret": "This is very secret" + } +} +``` + + +**Query `secret` (with role set as `user`):** + +Go to `index.js:45`, change `admin` by `user` and reload the server. + +```graphql +query { + secret +} +``` + +The server returns the following response: + +```json +{ + "data": { + "secret": null + }, + "errors": [ + { + "message": "You are not authorized. Expected roles: admin", + "locations": [ + { + "line": 1, + "column": 2 + } + ], + "path": [ + "secret" + ] + } + ] +} +``` diff --git a/examples/custom-directives/index.js b/examples/custom-directives/index.js new file mode 100644 index 0000000000..34e4d34e35 --- /dev/null +++ b/examples/custom-directives/index.js @@ -0,0 +1,48 @@ +const { GraphQLServer } = require("graphql-yoga"); + +const typeDefs = ` + directive @upper on FIELD_DEFINITION + directive @auth(roles: [String]) on FIELD_DEFINITION + + type Query { + hello: String! @upper + secret: String @auth(roles: ["admin"]) + } +`; + +const directiveResolvers = { + upper(next, src, args, context) { + return next().then(str => str.toUpperCase()); + }, + auth(next, src, args, context) { + const { roles } = context; // We asume has roles of current user in context; + const expectedRoles = args.roles || []; + if ( + expectedRoles.length === 0 || expectedRoles.some(r => roles.includes(r)) + ) { + // Call next to continues process resolver. + return next(); + } + + // We has two options here. throw an error or return null (if field is nullable). + throw new Error( + `You are not authorized. Expected roles: ${expectedRoles.join(", ")}` + ); + } +}; + +const resolvers = { + Query: { + hello: () => `Hello World`, + secret: () => `This is very secret` + } +}; + +const server = new GraphQLServer({ + typeDefs, + resolvers, + directiveResolvers, + context: () => ({ roles: ["admin"] }) +}); + +server.start(() => console.log("Server is running on localhost:4000")); diff --git a/examples/custom-directives/package.json b/examples/custom-directives/package.json new file mode 100644 index 0000000000..28d9e300c7 --- /dev/null +++ b/examples/custom-directives/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "start": "node ." + }, + "dependencies": { + "graphql-yoga": "1.2.0" + } +}