GraphQL constraint directive ^+^
Allows using @constraint
directive to validate GraphQL resolver input data.
Inspired by Constraints Directives RFC and OpenAPI
This library is an extension of graphql-constraint-directive
This library by default uses validator for string validation.
We include a test suite that covers using all constraint directives (See running tests), except for the List constraint validator (WIP).
If you add support for validating additional string formats (or other validations), please make sure to add tests to cover success/failure cases before making a PR.
Install
npm install graphql-constraint-directive-plus
Usage
const ConstraintDirective = ;const express = ;const bodyParser = ;const graphqlExpress = ;const makeExecutableSchema = ;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") }`;const schema = ;const app = ; app;
Integrations
Syncing validation for:
- entity models (on mutation save)
- forms (on field change or submit)
You can use the constraints of this library with graphql-typeorm-validation to generate decorators for [class-validator] that can be used on any entity model, such as a TypeOrm entity
Use this with a typeorm connection
to build an entity class map, where each map entry is a model class with matching class-validator validation decorators applied.
The validation constraints are extracted via graphGenTypeorm from a GraphQL type definition
; const entityClassMap = ;const Post = entityClassMap;// ...
First your createPost
mutation resolver applies directive constraints on input object.
It can then call createPostModel
to create an in-memory Post
model, validate
it and then save
it to the typeorm storage repository.
const createPostModel = { try let postRepository = connection; let post = ; posttitle = title; posttext = text; await ; await postRepository; return post catch err }
Mapping to general entity models
graphGenTypeorm uses graphSchemaToJson to first convert GraphQL type definitions (schema) to a Javascript object (JSON).
You could feed this schema object directly to the mapper.
This can be done using the decorate class API which lets you decorate any entity class with class-validator
decorators.
Syncing validations with forms
You can further use the schema object to generate Yup form validations, using custom models in json-schema-to-yup.
Yup goes hand-in-glove with Formik, the amazing form builder. Ideally you would then also generate the form, mapping model/type fields to form fields...
We then need to use Yup addMethod to create additional Yup validators to match those available for the @constraint
directive
const validator = ; Yup;
With a little "trickery", you can sync your validations across:
- models
- mutation resolvers
- forms
Would love to see your contributions to make this dream into reality. Almost there!
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:
alpha-numeric
alpha
ascii
byte
credit-card
currency-amount
data-uri
date-time
date
domain-name
email
hash
hex-color
ipv4
ipv6
isbn
magnet-uri
mime-type
mobile-phone
mongo-id
postal-code
uri
uuid
Format validator options can be set as additional directive arguments:
@
Format options available (with default values):
alphaLocale
(string)postalLocale
(string)phoneLocale
(string)hashAlgo
(string)domainName
(object)email
(object)currency
(object)
locale: "en-US" // used by alpha, alphanumeric, postalCode and mobilePhone hashAlgo: "md5" domainName: require_tld: true allow_underscores: false allow_trailing_dot: false email: allow_display_name: false require_display_name: false allow_utf8_local_part: true require_tld: true allow_ip_domain: false domain_specific_validation: false currency: symbol: "$" require_symbol: false allow_space_after_symbol: false symbol_after_digits: false allow_negatives: true parens_for_negatives: false negative_sign_before_digits: false negative_sign_after_digits: false allow_negative_sign_placeholder: false thousands_separator: "," decimal_separator: "." allow_decimal: true require_decimal: false digits_after_decimal: 2 allow_space_after_digits: false
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
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 = { if errororiginalError && errororiginalErrorcode === "ERR_GRAPHQL_CONSTRAINT_VALIDATION" // return a custom object return error;}; app;
Customization
By default, the constraint directive uses validator.js
You can pass your own validator
in the GraphQL context object, conforming to this API:
isLength(value)
contains(value)
isAlphanumeric(value, locale)
isAlpha(value, locale)
isAscii(value)
isByte(value)
isCreditCard(value)
isCurrency(value)
isDataUri(value)
isDateTime(value)
isDate(value)
isDomainName(value)
isEmail(value)
isHash(value)
isHexColor(value)
isIPv6(value)
isIPv4(value)
isIsbn(value)
isMagnetUri(value)
isMimeType(value)
isMobilePhone(value, locale)
isMongoId(value)
- `isPostalCode(value, countryCode)
isUri(value)
isUUID(value)
Note: All the above methods expect value
to be a string.
The default validator is wrapped as follows:
const wrappedValidator = // wrap your own validator using the same API isLength: validatorisLength contains: validatorcontains isAlpha: validatorisAlpha isAlphanumeric: validatorisAlphanumeric isAscii: validatorisAscii isByte: validatorisBase64 isCreditCard: validatorisCreditCard isCurrency: validatorisCurrency isDataUri: validatorisDataURI isDateTime: validatorisRFC3339 isDate: validatorisISO8601 isDomainName: validatorisFQDN isEmail: validatorisEmail isHash: validatorisHash isHexColor: validatorisHexColor validator validator isIsbn: validatorisISBN isMagnetUri: validatorisMagnetURI isMobilePhone: validatorisMobilePhone isMongoId: validatorisMongoId isMimeType: validatorisMimeType isPostalCode: validatorisPostalCode isUri: validatorisURL isUUID: validatorisUUID;
Validation messages
You can set a validationError
function map on the GraphQL context object to provide your own validator error handlers.
format(key, value)
- `string(name, msg, args[])
- `number(name, msg, args[])
The format validators will call: validationError.format('date', value)
The string
and number
validators will call the error handler like this:
validationError;
Note that the third argument contains a list where each object has an arg
entry that indicates the constraint that failed. You can use this as a key to lookup in your own validation error message map to return or output a localized error message as you see fit.
Reusing validators
You can re-use the core validators as follows:
;;; const field = "name";const args = minLength: 4 maxLength: 40 ;value = "John Smith"; string;
Complex types
See Complex types
Development
Tests
Run npm run test:nolint
to run all tests without linting
Resources
- GraphQL List - How to use arrays in GraphQL schema (GraphQL Modifiers)
- Deep dive into GraphQL type system
- Life of a GraphQL Query — Validation
- Graphql validated types
License
See License