Créer des types personnalisés avec GraphQLScalarType

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.
graphqlhub-logo
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é GraphQLIntet 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 donc GraphQLString,
  • 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 et parseValue 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 noeud AST* 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
[actionbox color=”default” title=”” description=”JS-REPUBLIC est une société de services spécialisée dans le développement JavaScript. Nous sommes centre de formation agréé. Retrouvez toutes nos formations techniques sur notre site partenaire dédié au Training” btn_label=”Nos formations” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external=”1″]