Giter Club home page Giter Club logo

graphql-constraint-directive's Introduction

graphql-constraint-directive

Build Status Coverage Status Known Vulnerabilities

Allows using @constraint as a directive to validate input data. Inspired by Constraints Directives RFC and OpenAPI

Install

npm install graphql-constraint-directive

For GraphQL v15 and below, use v2 of this package

npm install graphql-constraint-directive@v2

Usage

There are multiple ways to make use of the constraint directive in your project. Below outlines the benefits and caveats. Please choose the most appropriate to your use case.

Schema wrapper

Implementation based on schema wrappers - basic scalars are wrapped as custom scalars with validations.

Benefits

  • based on graphql library, works everywhere
  • posibility to also validate GraphQL response data

Caveats

  • modifies GraphQL schema, basic scalars (Int, Float, String) are replaced by custom scalars
const { constraintDirective, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { makeExecutableSchema } = require('@graphql-tools/schema')
const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`

let schema = makeExecutableSchema({
  typeDefs: [constraintDirectiveTypeDefs, typeDefs],
})
schema = constraintDirective()(schema)

const app = express()
const server = new ApolloServer({ schema })

await server.start()

server.applyMiddleware({ app })

Server plugin

Implementation based on server plugin. Common server plugins are implemented, function validateQuery(schema, query, variables, operationName) can be used to implement additional plugins.

Benefits

  • schema stays unmodified

Caveats

  • runs only in supported servers
  • validates only GraphQL query, not response data

Envelop

Use as an Envelop plugin in supported frameworks, e.g. GraphQL Yoga. Functionality is plugged in execute phase

const { createEnvelopQueryValidationPlugin, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
const express = require('express')
const { createServer } = require('@graphql-yoga/node')
const { makeExecutableSchema } = require('@graphql-tools/schema')

const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`

let schema = makeExecutableSchema({
  typeDefs: [constraintDirectiveTypeDefs, typeDefs],
})

const app = express()

const yoga = createServer({
    schema,
    plugins: [createEnvelopQueryValidationPlugin()],
    graphiql: false
})

app.use('/', yoga)

app.listen(4000);

Apollo 3 Server

As an Apollo 3 Server plugin

const { createApolloQueryValidationPlugin, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { makeExecutableSchema } = require('@graphql-tools/schema')

const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`

let schema = makeExecutableSchema({
  typeDefs: [constraintDirectiveTypeDefs, typeDefs],
})

const plugins = [
  createApolloQueryValidationPlugin({
    schema
  })
]

const app = express()
const server = new ApolloServer({ 
  schema,
  plugins
})

await server.start()

server.applyMiddleware({ app })

Apollo 4 Server

As an Apollo 4 Server plugin

const { createApollo4QueryValidationPlugin, constraintDirectiveTypeDefs } = require('graphql-constraint-directive/apollo4')
const express = require('express')
const { ApolloServer } = require('@apollo/server')
const { makeExecutableSchema } = require('@graphql-tools/schema')
const cors = require('cors')
const { json } = require('body-parser')

const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`

let schema = makeExecutableSchema({
  typeDefs: [constraintDirectiveTypeDefs, typeDefs],
})

const plugins = [
  createApollo4QueryValidationPlugin({
    schema
  })
]

const app = express()
const server = new ApolloServer({ 
  schema,
  plugins
})

await server.start()

app.use(
    '/',
    cors(),
    json(),
    expressMiddleware(server)
  )

Apollo 4 Subgraph server

There is a small change required to make the Apollo Server quickstart work when trying to build an Apollo Subgraph Server. We must use the buildSubgraphSchema function to build a schema that can be passed to an Apollo Gateway/supergraph, instead of makeExecuteableSchema. This uses makeExecutableSchema under the hood.

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { createApollo4QueryValidationPlugin, constraintDirectiveTypeDefsGql } from 'graphql-constraint-directive/apollo4';

const typeDefs = gql`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])

  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }
`;

const schema = buildSubgraphSchema({
  typeDefs: [constraintDirectiveTypeDefsGql, typeDefs]
});

const plugins = [
  createApollo4QueryValidationPlugin({
    schema
  })
]

const server = new ApolloServer({
  schema,
  plugins
});

await startStandaloneServer(server);

Express

This implementation is untested now, as express-graphql module is not maintained anymore.

As a Validation rule when query variables are available

const { createQueryValidationRule, constraintDirectiveTypeDefs } = require('graphql-constraint-directive')
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { makeExecutableSchema } = require('@graphql-tools/schema')

const typeDefs = `
  type Query {
    books: [Book]
  }
  type Book {
    title: String
  }
  type Mutation {
    createBook(input: BookInput): Book
  }
  input BookInput {
    title: String! @constraint(minLength: 5, format: "email")
  }`

let schema = makeExecutableSchema({
  typeDefs: [constraintDirectiveTypeDefs, typeDefs],
})

const app = express()

app.use(
  '/api',
  graphqlHTTP(async (request, response, { variables }) => ({
    schema,
    validationRules: [
      createQueryValidationRule({
        variables
      })
    ]
  }))
)
app.listen(4000);

API

String

minLength

@constraint(minLength: 5) Restrict to a minimum length

maxLength

@constraint(maxLength: 5) Restrict to a maximum length

startsWith

@constraint(startsWith: "foo") Ensure value starts with foo

endsWith

@constraint(endsWith: "foo") Ensure value ends with foo

contains

@constraint(contains: "foo") Ensure value contains foo

notContains

@constraint(notContains: "foo") Ensure value does not contain foo

pattern

@constraint(pattern: "^[0-9a-zA-Z]*$") Ensure value matches regex, e.g. alphanumeric

format

@constraint(format: "email") Ensure value is in a particular format

Supported formats:

  • byte: Base64
  • date-time: RFC 3339
  • date: ISO 8601
  • email
  • ipv4
  • ipv6
  • uri
  • uuid

Int/Float

min

@constraint(min: 3) Ensure value is greater than or equal to

max

@constraint(max: 3) Ensure value is less than or equal to

exclusiveMin

@constraint(exclusiveMin: 3) Ensure value is greater than

exclusiveMax

@constraint(exclusiveMax: 3) Ensure value is less than

multipleOf

@constraint(multipleOf: 10) Ensure value is a multiple

Array/List

minItems

@constraint(minItems: 3) Restrict array/List to a minimum length

maxItems

@constraint(maxItems: 3) Restrict array/List to a maximum length

ConstraintDirectiveError

Each validation error throws a ConstraintDirectiveError. Combined with a formatError function, this can be used to customise error messages.

{
  code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
  fieldName: 'theFieldName',
  context: [ { arg: 'argument name which failed', value: 'value of argument' } ]
}
const formatError = function (error) {
  const code = error?.originalError?.originalError?.code || error?.originalError?.code || error?.code
  if (code === 'ERR_GRAPHQL_CONSTRAINT_VALIDATION') {
    // return a custom object
  }

  return error
}

app.use('/graphql', bodyParser.json(), graphqlExpress({ schema, formatError }))

Apollo Server 3

Throws a UserInputError for each validation error.

Apollo Server 4

Throws a prefilled GraphQLError with extensions.code set to BAD_USER_INPUT and http status code 400. In case of more validation errors, top level error is generic with Query is invalid, for details see extensions.validationErrors message, detailed errors are stored in extensions.validationErrors of this error.

Envelop

The Envelop plugin throws a prefilled GraphQLError for each validation error.

uniqueTypeName

@constraint(uniqueTypeName: "Unique_Type_Name") Override the unique type name generate by the library to the one passed as an argument. Has meaning only for Schema wrapper implementation.

graphql-constraint-directive's People

Contributors

confuser avatar renovate[bot] avatar dependabot[bot] avatar velias avatar kunhuangau avatar sakulstra avatar pukuba avatar kabytaa avatar ahmoha avatar yimiprod avatar vespaiach avatar koresar avatar tom-pearce avatar snyk-bot avatar schmidsi avatar pascalhelbig avatar simlu avatar ltphen avatar lkrzyzanek avatar kriskator avatar kschelonka avatar richard-julien avatar neolivz avatar hector-del-rio avatar

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.