GraphQL : des types personnalisés
GraphQL met à disposition l’objet GraphQLScalarType
qui permet de créer des types plus complexes et personnalisés que ceux proposés par défaut. Dans un premier temps nous allons mettre en place un serveur GraphQL
avec Express
, puis détailler l’utilisation de GraphQLScalarType
.
GraphQL propose de contrôler les données via différents objets représentant chacun un type de données.
Voici une liste non exhaustive de ces derniers:
GraphQL met également à disposition l’objet GraphQLScalarType
qui va nous permettre de créer nos propre types ! C’est cet objet qui va nous intéresser ici.
Mais avant de créer notre type personnalisé avec GraphQLScalarType
, nous allons d’abord mettre en place un serveur GraphQL avec Express pour tester notre type personalisé.
Un peu de code:
// server.js
const datas = require('./datas.json')
const express = require('express')
const graphQLHTTP = require('express-graphql')
const {
GraphQLObjectType
} = require('graphql')
const app = express()
const PORT = 8083
app.listen(PORT, _ => console.log(`server running on port %s`, PORT))
Nous avons importé express
, express-graphql
et graphql
puis mis en place notre serveur en écoute sur le port 8083
.
const {
GraphQLObjectType,
GraphQLInt,
GraphQLString
} = require('graphql')
const UserType = new GraphQLObjectType({
name: 'User',
description: 'Define a user',
fields: () => ({
'id': {
'type': GraphQLInt,
'description': 'User id'
},
'first_name': {
'type': GraphQLString,
'description': 'User first name'
},
'last_name': {
'type': GraphQLString,
'description': 'User last name'
},
'email': {
'type': GraphQLString,
'description': 'User email'
},
'gender': {
'type': GraphQLString,
'description': 'User gender'
}
})
})
Nous avons créé un type User
avec la classe GraphQLObjectType
sur la base des données de notre datas.json
que nous utiliserons plus tard. Chaque champ est ici typé via les classes de type GraphQL. Nous avons utilisé GraphQLInt
et GraphQLString
sans oublier bien sûr de les importer.
const {
/* ... */
GraphQLSchema
} = require('graphql')
/* ... */
const query = new GraphQLObjectType({
name: 'Queries',
description: 'Define queries',
fields: () => ({
getUserByMail: {
type: UserType,
args: {
email: {
type: GraphQLString
}
},
resolve: (_, { email }) => datas.find(user => email === user.email)
}
})
})
const schema = new GraphQLSchema({
query
})
const app = express()
const PORT = 8083
app.use('/', graphQLHTTP({
schema: schema,
graphiql: true,
pretty: true,
formatError: error => ({
message: error.message,
locations: error.locations,
stack: error.stack
})
}))
La variable query
et plus particulièrement la méthode fields
qui l’a compose contient la liste des requêtes que nous mettons à disposition.
Elle se compose :
- d’un nom
getUserByMail,
- de l’argument qui devra etre utilisé
email,
- du type associé à cet argument, ici c’est un type
String
doncGraphQLString,
- d’un résolveur, la fonction
resolve
qui va récupérer les données de notre utilisateur.
Le résolveur getUserByMail
est maintenant en place et permet de requêter les données en fournissant un email en argument. Nous avons également utilisé le module graphql-js
et la fonction graphQLHTTP
comme middleware. Ici l’url racine "/"
est maintenant prise en charge par notre middleware et notre schema GraphQLSchema
.
Pour lancer le serveur il suffit d’exécuter la commande suivante:
node server.js #ou `yarn watch` si vous avez cloné le dépot (et que vous avez yarn en global)
L’interface graphique graphQL graphiql
est accessible via l’url http://localhost:8083
Nous pouvons tester que notre résolveur fonctionne correctement avec cette requête (à utiliser dans l’interface graphiql
):
{
user: getUserByMail(email: "ehuntern@huffingtonpost.com") {
id
first_name
last_name
}
}
user
en début de requête est un alias de notre resolver getUserByMail`
Maintenant que notre serveur fonctionne correctement, utilisons l’objet GraphQLScalarType
afin de créer un type personnalisé pour les emails. Un EmailType
.
const {
/* ... */
GraphQLScalarType,
GraphQLError,
Kind,
GraphQLSchema
} = require('graphql')
/** ... **/
const EmailType = new GraphQLScalarType({
name: 'Email',
serialize: value => value,
parseValue: value => value,
parseLiteral(ast) {
return ast.value
}
})
Nous avons donc créé une instance de l’objet EmailType
via le constructeur GraphQLScalarType
en lui donnant en paramètre:
- L’attribut
name
permettant de nommer notre nouveau type de données. - Les attributs
serialize
etparseValue
sont deux fonctions de “sérialization” qui permettent de s’assurer de la validité de la valeur d’entrée. parseLiteral
est une fonction qui va nous retourner un objet contenant les informations du noeudAST
* de notre valeur d’entrée.
Voir AST ou Abstract Syntax Tree ou en Français Arbre Syntaxique Abstrait
Voici un exemple de cet objet:
{
kind: 'StringValue',
value: 'ehuntern@huffingtonpost.com',
loc: {
start: 31,
end: 60
}
}
Voir son AST
C’est donc dans cette fonction parseLiteral
que nous allons mettre en place les contrôles grâce à cet objet ast.
Nous avons accès au type de noeud AST
avec l’attribut kind
et à sa valeur avec l’attribut value
.
parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Email must be a string ' + ast.kind, [ast]);
}
if (!ast.value.match(/@/)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value
}
Ici nous commencons par contrôler que notre valeur est bien de type String
, via l’enum GraphQL Kind
, qui nous met à disposition la liste des différents types de noeud AST
.
Ensuite nous faisons une vérification sommaire sur la valeur à contrôler puis, si les deux conditions sont remplies, nous retournons notre valeur !
EmailType
est terminé et nous pouvons maintenant l’utiliser dans la description de la query:
const query = new GraphQLObjectType({
name: 'Queries',
description: 'Define queries',
fields: () => ({
getUserByMail: {
type: UserType,
args: {
email: {
type: EmailType // Ici
}
},
resolve: (_, { email }) => datas.find(user => email === user.email)
}
})
})
L’email en argument doit maintenant correspondre à sont propre type EmailType
!
Pour conclure, on peu dire que GraphQLScalarType
est très pratique, simple d’utilisation et nous permet de créer nos propres types rapidement.
Cet objet nous permet de contrôler les inputs en amont et ainsi éviter les méthodes de vérification habituelles du type isEmail()
, isPhone()
, isBlabla()
etc…
Thomas Rimbault, Développeur chez JS-Republic
Le github contenant la source
yarn install
yarn watch
- Jeu de données généré avec Mockaroo