Skip to content

Commit 7720d71

Browse files
authored
feat: Added support for custom directives (directive resolvers)
2 parents 5388487 + 85eb8a7 commit 7720d71

File tree

6 files changed

+158
-2
lines changed

6 files changed

+158
-2
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# custom-directive
2+
3+
This directory contains a simple GraphQL with custom directives example based on `graphql-yoga`.
4+
5+
## Get started
6+
7+
**Clone the repository:**
8+
9+
```sh
10+
git clone https://github.com/graphcool/graphql-yoga/
11+
cd graphql-yoga/examples/custom-directives
12+
```
13+
14+
**Install dependencies and run the app:**
15+
16+
```sh
17+
yarn install # or npm install
18+
yarn start # or npm start
19+
```
20+
21+
## Testing
22+
23+
Open your browser at [http://localhost:4000](http://localhost:4000) and start sending queries.
24+
25+
**Query `hello`:**
26+
27+
```graphql
28+
query {
29+
hello
30+
}
31+
```
32+
33+
The server returns the following response:
34+
35+
```json
36+
{
37+
"data": {
38+
"hello": "HELLO WORD"
39+
}
40+
}
41+
```
42+
43+
Note that the original `Hello Word` output from the resolver is now in upper case due to our custom `@upper` directive.
44+
45+
**Query `secret` (with role set as `admin`):**
46+
```graphql
47+
query {
48+
secret
49+
}
50+
```
51+
52+
The server returns the following response:
53+
54+
```json
55+
{
56+
"data": {
57+
"secret": "This is very secret"
58+
}
59+
}
60+
```
61+
62+
63+
**Query `secret` (with role set as `user`):**
64+
65+
Go to `index.js:45`, change `admin` by `user` and reload the server.
66+
67+
```graphql
68+
query {
69+
secret
70+
}
71+
```
72+
73+
The server returns the following response:
74+
75+
```json
76+
{
77+
"data": {
78+
"secret": null
79+
},
80+
"errors": [
81+
{
82+
"message": "You are not authorized. Expected roles: admin",
83+
"locations": [
84+
{
85+
"line": 1,
86+
"column": 2
87+
}
88+
],
89+
"path": [
90+
"secret"
91+
]
92+
}
93+
]
94+
}
95+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const { GraphQLServer } = require("graphql-yoga");
2+
3+
const typeDefs = `
4+
directive @upper on FIELD_DEFINITION
5+
directive @auth(roles: [String]) on FIELD_DEFINITION
6+
7+
type Query {
8+
hello: String! @upper
9+
secret: String @auth(roles: ["admin"])
10+
}
11+
`;
12+
13+
const directiveResolvers = {
14+
upper(next, src, args, context) {
15+
return next().then(str => str.toUpperCase());
16+
},
17+
auth(next, src, args, context) {
18+
const { roles } = context; // We asume has roles of current user in context;
19+
const expectedRoles = args.roles || [];
20+
if (
21+
expectedRoles.length === 0 || expectedRoles.some(r => roles.includes(r))
22+
) {
23+
// Call next to continues process resolver.
24+
return next();
25+
}
26+
27+
// We has two options here. throw an error or return null (if field is nullable).
28+
throw new Error(
29+
`You are not authorized. Expected roles: ${expectedRoles.join(", ")}`
30+
);
31+
}
32+
};
33+
34+
const resolvers = {
35+
Query: {
36+
hello: () => `Hello World`,
37+
secret: () => `This is very secret`
38+
}
39+
};
40+
41+
const server = new GraphQLServer({
42+
typeDefs,
43+
resolvers,
44+
directiveResolvers,
45+
context: () => ({ roles: ["admin"] })
46+
});
47+
48+
server.start(() => console.log("Server is running on localhost:4000"));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"scripts": {
3+
"start": "node ."
4+
},
5+
"dependencies": {
6+
"graphql-yoga": "1.2.0"
7+
}
8+
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class GraphQLServer {
5353
if (props.schema) {
5454
this.executableSchema = props.schema
5555
} else if (props.typeDefs && props.resolvers) {
56-
let { typeDefs, resolvers } = props
56+
let { directiveResolvers, typeDefs, resolvers } = props
5757

5858
// read from .graphql file if path provided
5959
if (typeDefs.endsWith('graphql')) {
@@ -70,6 +70,7 @@ export class GraphQLServer {
7070
? { Upload: GraphQLUpload }
7171
: {}
7272
this.executableSchema = makeExecutableSchema({
73+
directiveResolvers,
7374
typeDefs,
7475
resolvers: {
7576
...uploadMixin,

src/lambda.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class GraphQLServerLambda {
2626
if (props.schema) {
2727
this.executableSchema = props.schema
2828
} else if (props.typeDefs && props.resolvers) {
29-
let { typeDefs, resolvers } = props
29+
let { directiveResolvers, typeDefs, resolvers } = props
3030

3131
// read from .graphql file if path provided
3232
if (typeDefs.endsWith('graphql')) {
@@ -42,6 +42,7 @@ export class GraphQLServerLambda {
4242
}
4343

4444
this.executableSchema = makeExecutableSchema({
45+
directiveResolvers,
4546
typeDefs,
4647
resolvers,
4748
})

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
GraphQLTypeResolver,
99
ValidationContext,
1010
} from 'graphql'
11+
import { IDirectiveResolvers } from 'graphql-tools/dist/Interfaces'
1112
import { SubscriptionOptions } from 'graphql-subscriptions/dist/subscriptions-manager'
1213
import { LogFunction } from 'apollo-server-core'
1314

@@ -68,13 +69,15 @@ export interface Options extends ApolloServerOptions {
6869
}
6970

7071
export interface Props {
72+
directiveResolvers?: IDirectiveResolvers<any, any>
7173
typeDefs?: string
7274
resolvers?: IResolvers
7375
schema?: GraphQLSchema
7476
context?: Context | ContextCallback
7577
}
7678

7779
export interface LambdaProps {
80+
directiveResolvers?: IDirectiveResolvers<any, any>
7881
typeDefs?: string
7982
resolvers?: IResolvers
8083
schema?: GraphQLSchema

0 commit comments

Comments
 (0)