Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directives support with @Directive decorator #369

Merged
merged 17 commits into from
Nov 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **Breaking Change**: update `graphql-js` peer dependency to `^14.5.8`
- **Breaking Change**: update `graphql-query-complexity` dependency to `^0.4.0` and drop support for `fieldConfigEstimator` (use `fieldExtensionsEstimator` instead)
- update `TypeResolver` interface to match with `GraphQLTypeResolver` from `graphql-js`
- add basic support for directives with `@Directive()` decorator (#369)
### Fixes
- refactor union types function syntax handling to prevent possible errors with circular refs

Expand Down
107 changes: 107 additions & 0 deletions docs/directives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: Directives
---

[GraphQL directives](https://www.apollographql.com/docs/graphql-tools/schema-directives/) though the syntax might remind the TS decorators:

> A directive is an identifier preceded by a @ character, optionally followed by a list of named arguments, which can appear after almost any form of syntax in the GraphQL query or schema languages.

But in fact, they are a purely SDL (Schema Definition Language) feature that allows you to put some metadata for selected type or its field:

```graphql
type Foo @auth(requires: USER) {
field: String!
}

type Bar {
field: String! @auth(requires: USER)
}
```

That metadata can be read in runtime to modify the structure and behavior of a GraphQL schema to support reusable code and tasks like authentication, permission, formatting, and plenty more. They are also really useful for some external services like [Apollo Cache Control](https://www.apollographql.com/docs/apollo-server/performance/caching/#adding-cache-hints-statically-in-your-schema) or [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/#federated-schema-example).

**TypeGraphQL** of course provides some basic support for using the schema directives via the `@Directive` decorator.

## Usage

### Declaring in schema

Basically, there are two supported ways of declaring the usage of directives:

- string based - just like in SDL, with the `@` syntax:

```typescript
@Directive('@deprecated(reason: "Use newField")')
```

- object based - using a JS object to pass the named arguments

```typescript
@Directive("deprecated", { reason: "Use newField" }) // syntax without `@` !!!
```

Currently, you can use the directives only on object types, input types and their fields, as well as queries and mutations.

So the `@Directive` decorator can be placed over the class property/method or over the class itself, depending on the needs and the placements supported by the implementation:

```typescript
@Directive("@auth(requires: USER)")
@ObjectType()
class Foo {
@Field()
field: string;
}

@ObjectType()
class Bar {
@Directive("auth", { requires: "USER" })
@Field()
field: string;
}

@Resolver()
class FooBarResolver {
@Directive("@auth(requires: USER)")
@Query()
foobar(@Arg("baz") baz: string): string {
return "foobar";
}
}
```

> Note that even as directives are a purely SDL thing, they won't appear in the generated schema definition file. Current implementation of directives in TypeGraphQL is using some crazy workarounds because [`graphql-js` doesn't support setting them by code](https://github.com/graphql/graphql-js/issues/1343) and the built-in `printSchema` utility omits the directives while printing.

Also please note that `@Directive` can only contain a single GraphQL directive name or declaration. If you need to have multiple directives declared, just place multiple decorators:

```typescript
@ObjectType()
class Foo {
@Directive("lowercase")
@Directive('@deprecated(reason: "Use `newField`")')
@Directive("hasRole", { role: Role.Manager })
@Field()
bar: string;
}
```

### Providing the implementation

Besides declaring the usage of directives, you also have to register the runtime part of the used directives.

> Be aware that TypeGraphQL doesn't have any special way for implementing schema directives. You should use some [3rd party libraries](https://www.apollographql.com/docs/graphql-tools/schema-directives/#implementing-schema-directives) depending on the tool set you use in your project, e.g. `graphql-tools` or `ApolloServer`.

Here is an example using the [`graphql-tools`](https://github.com/apollographql/graphql-tools):

```typescript
import { SchemaDirectiveVisitor } from "graphql-tools";

// build the schema as always
const schema = buildSchemaSync({
resolvers: [SampleResolver],
});

// register the used directives implementations
SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
sample: SampleDirective,
});
```
Loading