Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/confuser/graphql-constraint-directive
Validate GraphQL fields
https://github.com/confuser/graphql-constraint-directive
constraint directive graphql nodejs validation validator
Last synced: 3 months ago
JSON representation
Validate GraphQL fields
- Host: GitHub
- URL: https://github.com/confuser/graphql-constraint-directive
- Owner: confuser
- License: isc
- Created: 2018-05-16T13:39:48.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-10-30T00:16:06.000Z (3 months ago)
- Last Synced: 2024-10-30T01:51:09.709Z (3 months ago)
- Topics: constraint, directive, graphql, nodejs, validation, validator
- Language: JavaScript
- Size: 777 KB
- Stars: 568
- Watchers: 6
- Forks: 74
- Open Issues: 31
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-graphql - GraphQL Constraint Directive - Allows using @constraint as a directive to validate input data. Inspired by Constraints Directives RFC and OpenAPI (Implementations / JavaScript/TypeScript)
- awesome-list - graphql-constraint-directive
README
# graphql-constraint-directive
[![Build Status](https://github.com/confuser/graphql-constraint-directive/actions/workflows/build.yaml/badge.svg)](https://github.com/confuser/graphql-constraint-directive/actions/workflows/build.yaml)
[![Coverage Status](https://coveralls.io/repos/github/confuser/graphql-constraint-directive/badge.svg?branch=master)](https://coveralls.io/github/confuser/graphql-constraint-directive?branch=master)
[![Known Vulnerabilities](https://snyk.io/test/github/confuser/graphql-constraint-directive/badge.svg?targetFile=package.json)](https://snyk.io/test/github/confuser/graphql-constraint-directive?targetFile=package.json)Allows using @constraint as a directive to validate input data. Inspired by [Constraints Directives RFC](https://github.com/APIs-guru/graphql-constraints-spec) 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```js
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](https://www.envelop.dev) in supported frameworks, e.g. [GraphQL Yoga](https://www.graphql-yoga.com/).
Functionality is plugged in `execute` phaseThis plugin requires the following dependencies installed in your project:
* `@envelop/core` - `^2.0.0````js
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](https://www.apollographql.com/docs/apollo-server/v3) plugin
This plugin requires the following dependencies installed in your project:
* dependencies required for your selected Apollo Server 3 variant```js
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](https://www.apollographql.com/docs/apollo-server/v4) plugin
This plugin requires the following dependencies installed in your project:
* `@apollo/server` - `^4.0.0`
* `graphql-tag` - `^2.0.0````js
const { createApollo4QueryValidationPlugin, constraintDirectiveTypeDefs } = require('graphql-constraint-directive/apollo4')
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone');
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 = [
createApollo4QueryValidationPlugin()
]const server = new ApolloServer({
schema,
plugins
})await startStandaloneServer(server);
```
#### Apollo 4 Subgraph serverThere is a small change required to make the Apollo Server quickstart work when trying to build an [Apollo Subgraph Server](https://www.apollographql.com/docs/federation/building-supergraphs/subgraphs-apollo-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.This plugin requires the following dependencies installed in your project:
* `@apollo/server` - `^4.0.0`
* `graphql-tag` - `^2.0.0````ts
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()
]const server = new ApolloServer({
schema,
plugins
});await startStandaloneServer(server);
```#### Express
*This implementation is untested now, as [`express-graphql` module](https://github.com/graphql/express-graphql) is not maintained anymore.*
As a [Validation rule](https://graphql.org/graphql-js/validation/) when query `variables` are available
```js
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);```
### Schema documentationYou can use the provided schema transformation to automatically add `@constraint` documentation into fields and arguments descriptions. By default directives are not typically present in the exposed introspected schema
```js
const { constraintDirectiveTypeDefs, constraintDirectiveDocumentation } = require('graphql-constraint-directive')
const { makeExecutableSchema } = require('@graphql-tools/schema')const typeDefs = ...
let schema = makeExecutableSchema({
typeDefs: [constraintDirectiveTypeDefs, typeDefs]
})schema = constraintDirectiveDocumentation()(schema);
// any constraint directive handler implementation
```This transformation appends `constraint documentation header`, and then a list of `constraint conditions descriptions` to the description of each field and argument where the `@constraint` directive is used.
Original schema:
```graphql
"""
Existing field or argument description.
"""
fieldOrArgument: String @constraint(minLength: 10, maxLength: 50)
```Transformed schema:
```graphql
"""
Existing field or argument description.*Constraints:*
* Minimum length: `10`
* Maximum length: `50`
"""
fieldOrArgument: String @constraint(minLength: 10, maxLength: 50)
```[CommonMark](https://spec.commonmark.org) is used in the desccription for better readability.
If `constraint documentation header` already exists in the field or argument description, then
constraint documentation is not appended. This allows you to override constraint description
when necessary, or use this in a chain of subgraph/supergraph schemes.Both `constraint documentation header` and `constraint conditions descriptions` can be customized
during the transformation creation, eg. to localize them.```js
schema = constraintDirectiveDocumentation(
{
header: '*Changed header:*',
descriptionsMap: {
minLength: 'Changed Minimum length',
maxLength: 'Changed Maximum length',
startsWith: 'Changed Starts with',
endsWith: 'Changed Ends with',
contains: 'Changed Contains',
notContains: 'Changed Doesn\'t contain',
pattern: 'Changed Must match RegEx pattern',
format: 'Changed Must match format',
min: 'Changed Minimum value',
max: 'Changed Maximum value',
exclusiveMin: 'Changed Grater than',
exclusiveMax: 'Changed Less than',
multipleOf: 'Changed Must be a multiple of',
minItems: 'Changed Minimum number of items',
maxItems: 'Changed Maximum number of items'
}
}
)(schema);
```## 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 formatSupported formats:
- byte: Base64
- date-time: RFC 3339
- date: ISO 8601
- ipv4
- ipv6
- uri
- uuid#### Custom Format
You can add your own custom formats by passing a `formats` object to the plugin options. See example below.```@constraint(format: "my-custom-format")```
```js
const formats = {
'my-custom-format': (value) => {
if (value === 'foo') {
return true
}throw new GraphQLError('Value must be foo')
}
};// Envelop
createEnvelopQueryValidationPlugin({ formats })// Apollo 3 Server
createApolloQueryValidationPlugin({ formats })// Apollo 4 Server
createApollo4QueryValidationPlugin({ formats })
```### 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.```js
{
code: 'ERR_GRAPHQL_CONSTRAINT_VALIDATION',
fieldName: 'theFieldName',
context: [ { arg: 'argument name which failed', value: 'value of argument' } ]
}
``````js
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`](https://www.apollographql.com/docs/apollo-server/data/errors/#bad_user_input) 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.