We're looking into using GraphQL for version 2 of a headless CMS we're developing.
In version 1 of this CMS, we used JSON Schema to validate each document against a schema before being saved in the database -- for example, if it's a blog article it'd be validated against the Article
schema, and if it's a roundup ("best of" list) it'd be validated against the Roundup
schema.
For version 2, we're contemplating using GraphQL for the API. And then it occurred to us that the GraphQL schema is basically parallel to the JSON Schema -- it describes the document structure, field types, etc.
So we could simply have "one source of schema truth", the GraphQL schema, and use this both for querying documents and for validating new documents when a new revision is being saved. (Note that I'm talking about validating JSON data against a GraphQL schema, not validating a GraphQL query against a schema.)
I figure the data would be validated against all the fields in the schema, except deprecated fields, because you only want to validate against the "latest version" of the fields.
We could do one of three things:
- Use the GraphQL AST directly to validate a document, i.e., write a data validator ourselves.
- Use the GraphQL AST to generate a JSON Schema, and use a standard JSON Schema validator to actually validate it.
- Just accept that GraphQL isn't quite the right fit for validation, and define the schema twice -- once in GraphQL for querying, and again in JSON Schema for validation (annoying and error-prone to keep them in sync).
Questions: Are #1 and #2 silly ideas? Are there any GraphQL tools which do this kind of data validation? Are there any other ways to achieve this without defining the schema twice?
For reference, our backend will be written in Python but the admin UI will be client-side React and JavaScript. This is a cut-down version of the kind of GraphQL schema we're talking about (supports "Article" and "Roundup" document types):
schema {
query: Query
}
type Query {
documents: [Document!]!
document(id: Int): Document!
}
interface Document {
id: Int!
title: String!
}
type Article implements Document {
id: Int!
title: String!
featured: Boolean!
sections: [ArticleSection!]!
}
union ArticleSection = TextSection | PhotoSection | VideoSection
type TextSection {
content: String!
heading: String
}
type PhotoSection {
sourceUrl: String!
linkUrl: String
caption: String
content: String
}
type VideoSection {
url: String!
}
type Roundup implements Document {
id: Int!
title: String!
isAward: Boolean!
intro: String
hotels: [RoundupHotel!]!
}
type RoundupHotel {
url: String!
photoUrl: String @deprecated(reason: "photoUrl is deprecated; use photos")
photos: [RoundupPhoto!]!
blurb: String!
title: String
}
type RoundupPhoto {
url: String!
caption: String
}