Giter Club home page Giter Club logo

graphql-directive's Introduction

graphql-directive

Build Status Code Coverage version MIT License

GraphQL supports several directives: @include, @skip and @deprecated. This module opens a new dimension by giving you the possibility to define your custom directives.

Custom directives have a lot of use-cases:

  • Formatting
  • Authentication
  • Introspection
  • ...

You can learn more about directives in GraphQL documentation.

Install

npm install graphql-directive

Steps

1. Define a directive in schema

A directive must be defined in your schema, it can be done using the keyword directive:

directive @dateFormat(format: String) on FIELD | FIELD_DEFINITION

This code defines a directive called dateFormat that accepts one argument format of type String. The directive can be used on FIELD (query) and FIELD_DEFINITION (schema).

FIELD AND FIELD_DEFINITION are the only two directive locations supported.

2. Add directive resolver

The second step consists in adding a resolver for the custom directive.

import { addDirectiveResolveFunctionsToSchema } from 'graphql-directive'

// Attach a resolver map to schema
addDirectiveResolveFunctionsToSchema(schema, {
  async dateFormat(resolve, source, args) {
    const value = await resolve()
    return format(new Date(value), args.format)
  },
})

3. Use directive in query

You can now use your directive either in schema or in query.

import { graphql } from 'graphql'

const QUERY = `{ publishDate @dateFormat(format: "DD-MM-YYYY") }`

const rootValue = { publishDate: '1997-06-12T00:00:00.000Z' }

graphql(schema, query, rootValue).then(response => {
  console.log(response.data) // { publishDate: '12-06-1997' }
})

Usage

addDirectiveResolveFunctionsToSchema(schema, resolverMap)

addDirectiveResolveFunctionsToSchema takes two arguments, a GraphQLSchema and a resolver map. It modifies the schema in place by attaching directive resolvers. Internally your resolvers are wrapped into another one.

import { addDirectiveResolveFunctionsToSchema } from 'graphql-directive'

const resolverMap = {
  // Will be called when a @upperCase directive is applied to a field.
  async upperCase(resolve) {
    const value = await resolve()
    return value.toString().toUpperCase()
  },
}

// Attach directive resolvers to schema.
addDirectiveResolveFunctionsToSchema(schema, resolverMap)

Directive resolver function signature

Every directive resolver accepts five positional arguments:

directiveName(resolve, obj, directiveArgs, context, info) { result }

These arguments have the following conventional names and meanings:

  1. resolve: Resolve is a function that returns the result of the directive field. For consistency, it always returns a promise resolved with the original field resolver.
  2. obj: The object that contains the result returned from the resolver on the parent field, or, in the case of a top-level Query field, the rootValue passed from the server configuration. This argument enables the nested nature of GraphQL queries.
  3. directiveArgs: An object with the arguments passed into the directive in the query or schema. For example, if the directive was called with @dateFormat(format: "DD/MM/YYYY"), the args object would be: { "format": "DD/MM/YYYY" }.
  4. context: This is an object shared by all resolvers in a particular query, and is used to contain per-request state, including authentication information, dataloader instances, and anything else that should be taken into account when resolving the query.
  5. info: This argument should only be used in advanced cases, but it contains information about the execution state of the query, including the field name, path to the field from the root, and more. It’s only documented in the GraphQL.js source code.

Examples of directives

Text formatting: @upperCase

Text formatting is a good use case for directives. It can be helpful to directly format your text in your queries or to ensure that a field has a specific format server-side.

import { buildSchema } from 'graphql'
import { addDirectiveResolveFunctionsToSchema } from 'graphql-directive'

// Schema
const schema = buildSchema(`
  directive @upperCase on FIELD_DEFINITION | FIELD
`)

// Resolver
addDirectiveResolveFunctionsToSchema(schema, {
  async upperCase(resolve) {
    const value = await resolve()
    return value.toUpperCase()
  },
})

See complete example

Date formatting: @dateFormat(format: String)

Date formatting is a CPU expensive operation. Since all directives are resolved server-side, it speeds up your client and it is easily cachable.

import { buildSchema } from 'graphql'
import { addDirectiveResolveFunctionsToSchema } from 'graphql-directive'
import format from 'date-fns/format'

// Schema
const schema = buildSchema(`
  directive @dateFormat(format: String) on FIELD_DEFINITION | FIELD
`)

// Resolver
addDirectiveResolveFunctionsToSchema(schema, {
  async dateFormat(resolve, source, args) {
    const value = await resolve()
    return format(new Date(value), args.format)
  },
})

See complete example

Authentication: @requireAuth

Authentication is a very good usage of FIELD_DEFINITION directives. By using a directive you can restrict only one specific field without modifying your resolvers.

import { buildSchema } from 'graphql'
import { addDirectiveResolveFunctionsToSchema } from 'graphql-directive'

// Schema
const schema = buildSchema(`
  directive @requireAuth on FIELD_DEFINITION
`)

// Resolver
addDirectiveResolveFunctionsToSchema(schema, {
  requireAuth(resolve, directiveArgs, obj, context, info) {
    if (!context.isAuthenticated)
      throw new Error(`You must be authenticated to access "${info.fieldName}"`)
    return resolve()
  },
})

See complete example

Limitations

Inspiration

License

MIT

graphql-directive's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-directive's Issues

argument directive

I added a new "directive" called "export". To use these directive values hold in memory. But I need one more "directive" to use these values. I named it "import". I want to do something like this. Is something like this possible. Is there a way to use "directive" in the Argument?

query{
    users(name: "Test"){
     user_id @export(as: "user_id")
  }  
  safe(user_id: @import(name: "user_id")){
    safe_id
    name
 }
}

Implementing a live directive

I'm trying to implement a live directive, which keeps the caller of the query up-to-date.
This is my current code, which does just resolve() with the directive.

const typeDefs = /* GraphQL */ `
  directive @live on FIELD | FIELD_DEFINITION

  type Todo {
    text: String!
    completed: Boolean!
  }

  type Query {
    allTodos: [Todo]! @live
  }
`;

const resolvers = {
  Query: {
    allTodos() {
      return someTodoArray;
    }
  }
};

const executableSchema = makeExecutableSchema({
  typeDefs,
  resolvers
});

addDirectiveResolveFunctionsToSchema(executableSchema, {
  live(resolve) {
    return resolve();
  }
});

How would I push new results to the caller with this approach?
Is there a way to get a reference to the caller to push new results?

ApolloClient example

Hi, just saw you PR to fix Apollos InMemoryCache to support custom directives, which landed in the master is part of the actual code base.
I am using ApolloClient and would like to implement a custom directive on the client side.

I would really appreciate, if you could drop in an example here, how you would implement the simplest directive for ApolloClient? Or maybe a hint where to look it up. I found only some ServerSide examples, and I am bit confused about the client side part. Thank you

Support latest graphql version?

I've noticed some issues if I'm using this with the latest graphql (0.12.3). If I try using it on a schema, the first error I get is:

Uncaught TypeError: Cannot read property 'FIELD_DEFINITION' of undefined

Which is coming from here. It seems that the latest graphql has updated some of the object mappings. (Seems like there are a lot of updates to the schema in 0.12.)

it should support query variable binding

so i have directive with variables
directive @url(root: String!) on FIELD
with simple resolver:

async url(resolve, source, directiveArgs) {
  return url.resolve(directiveArgs.root, await resolve())
},

so it should work with query variables binding like that
query($url: String!) { foo @url(root:$url) }

Cannot read property 'directives' of undefined at createFieldExecutionResolver

Hi, it throws me the following error when trying to add directive resolve functions to the schema.

I have checked with forEachField and astNode is always undefined, is it my configuration or is it a bug?

const schema = new GraphQLSchema({
  directives: [
    new GraphQLDirective({
      name: 'isAuth',
      locations: [ DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.FIELD ]
    })
  ],
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      user: {
        type: new GraphQLObjectType({
          name: 'User',
          fields: () => ({
            id: { type: GraphQLNonNull(GraphQLInt) },
            email: { type: GraphQLNonNull(GraphQLString) }
          })
        }),
        args: { id: { type: GraphQLInt } },
        resolve: (parent, args, context, info) => {
          return { id: 1, email: '[email protected]' };
        }
      }
    }
  })
});

const directiveResolvers = {
  isAuth(next, source, args, context, info) {
    throw Error('Ups!');
  }
};

const { addDirectiveResolveFunctionsToSchema } = require('graphql-directive');
addDirectiveResolveFunctionsToSchema(schema, directiveResolvers);

addDirectiveResolveFunctionsToSchema breaks default behavior of @skip and @include

Hi guys. I'm trying to implement my custom directives, and this package makes process so easy, but I've discovered that it breaks default behavior of @inclue and @skip

How to reproduce

Lets' take simple start app, from graphql-server-example. Change code little bit, and when I wrap schema with custom directive resolvers using addDirectiveResolveFunctionsToSchema, it breaks @skip(if: false) and @include(if: true), while @include(if: false) and @skip(if: true) still work.

const { ApolloServer, gql } = require('apollo-server')
const { addDirectiveResolveFunctionsToSchema } = require('graphql-directive')
const { makeExecutableSchema } = require('graphql-tools')

// This is a (sample) collection of books we'll be able to query
// the GraphQL server for.  A more complete example might fetch
// from an existing data source like a REST API or database.
const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
]

// Type definitions define the "shape" of your data and specify
// which ways the data can be fetched from the GraphQL server.
const typeDefs = gql`
  directive @upper on FIELD | FIELD_DEFINITION

  # Comments in GraphQL are defined with the hash (#) symbol.

  # This "Book" type can be used in other type declarations.
  type Book {
    title: String
    author: String
  }

  # The "Query" type is the root of all GraphQL queries.
  # (A "Mutation" type will be covered later on.)
  type Query {
    books: [Book]
  }
`

// Resolvers define the technique for fetching the types in the
// schema.  We'll retrieve books from the "books" array above.
const resolvers = {
  Query: {
    books: () => books,
  },
}


const directiveResolvers = {
  async upper (resolve, source, args, { locale }) {
    const value = await resolve()

    if (typeof value === 'string') {
      return value.toUpperCase()
    }

    return value
  },
}

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

addDirectiveResolveFunctionsToSchema(schema, directiveResolvers)


// In the most basic sense, the ApolloServer can be started
// by passing type definitions (typeDefs) and the resolvers
// responsible for fetching the data for those types.
const server = new ApolloServer({
  schema,
  engine: process.env.ENGINE_API_KEY && {
    apiKey: process.env.ENGINE_API_KEY,
  },
})

// This `listen` method launches a web-server.  Existing apps
// can utilize middleware options, which we'll discuss later.
server.listen().then(({ url }) => {
  console.log(`πŸš€  Server ready at ${url}`)
})

It throw error

"extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "stacktrace": [
            "TypeError: directiveInfo.resolver is not a function",
...

Do I miss something?
Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.