Fermer

octobre 8, 2020

Comment gérer les téléchargements de fichiers en réaction avec Google Storage et GraphQL


À propos de l'auteur

Nwani Victory travaille en tant qu'ingénieur frontend chez Liferithms.inc de Lagos, Nigeria. Après les heures de bureau, il se double d'un ingénieur cloud à la recherche de moyens de faire du cloud…
En savoir plus sur
Nwani

De la photo de profil d'un utilisateur à d'autres ressources multimédias, la collecte et le stockage de données aux services cloud via le téléchargement de fichiers sont devenus une fonctionnalité essentielle pour la plupart des applications modernes. Dans cet article, vous apprendrez comment les téléchargements de fichiers peuvent être implémentés dans une application GraphQL.

En exploitant React-Apollo cet article se concentre sur la façon dont une fonctionnalité de téléchargement de fichiers peut être ajoutée à une nouvelle ou existante application frontale alimentée par une API GraphQL. Pour ce faire, nous allons créer cette application de démonstration qui permet aux utilisateurs de télécharger une image de profil lors de la création d'un compte à côté de leur nom d'utilisateur préféré. Pendant que nous faisons cela, nous travaillerions progressivement à travers le processus de:

  • Création d'une application backend Node GraphQL capable d'accepter et d'envoyer le fichier téléchargé vers un bucket de stockage dans le Google Cloud.
  • Configuration d'une connexion à Google Cloud Storage.
  • Collecte des entrées de fichiers dans une application React et envoi à une application backend GraphQL à l'aide de React Apollo.

Note : Bien que tous les extraits de code soient expliqués, pour bien les comprendre, vous devez avoir une compréhension de la syntaxe es6 de JavaScript, GraphQL et React.js .

Cet article sera utile aux développeurs qui sont intéressés ou envisagent d'utiliser Google Cloud Storage pour les fichiers télécharge dans leur application React et Nodejs GraphQL. Bien que cet article ne soit pas une introduction à GraphQL chaque concept GraphQL utilisé dans cet article est expliqué et référencé pour une meilleure compréhension.

Configuration d'une API Node GraphQL

Nous allons construire une API GraphQL à consommer par notre application React. Cette application backend recevra l'image téléchargée par un utilisateur et enverra le fichier téléchargé vers Google Cloud Storage.

Pour commencer, nous utilisons les Apollo-Server-express et Express.js ] pour démarrer rapidement une API GraphQL. Nous pouvons le faire en exécutant les commandes suivantes:

 # Créer un nouveau dossier Projet et (&&) s'y déplacer
mkdir Node-GraphQL-API && cd Node-GraphQL-API

# Créer un nouveau projet Node
fil init -y

# Installez les deux dépendances nécessaires
fil ajouter apollo-server-express express

Ensuite, nous construisons un seul point de terminaison GraphQL, qui est accessible via le port 4000 .

 const express = require ('express')
const {ApolloServer} = require ('apollo-server-express')

const {Requêtes, Mutations, TypeDefs} = require ('./ resolvers')

résolveurs const = {
  Requête: requêtes,
  Mutation: mutations
}

const server = new ApolloServer ({TypeDefs, résolveurs});
 
const app = express ();
server.applyMiddleware ({app});
 
app.listen ({port: 4000}, () =>
  console.log (`Graphiql s'exécutant sur http: // localhost: 4000 / $ {server.graphqlPath}`));

Nous avons commencé par importer nos requêtes, mutations et définitions de type à partir du fichier des résolveurs, puis nous avons créé un objet resolvers contenant les requêtes et mutations importées puis l'avons passé dans le constructeur ApolloServer à côté de la définition de type importée.

Ensuite, nous avons créé une instance de express.js dans la variable app et l'avons intégrée au serveur apollo en appelant la méthode applyMiddleware . Selon la documentation de react-apollo sur la méthode applyMiddleware, cette intégration permet d'ajouter divers petits middlewares internes. Enfin, nous avons appelé la méthode listen sur l'instance express, lui indiquant d'écouter et de servir les connexions HTTP sur le port 4000. Nous avons également ajouté un rappel pour déconnecter un message indiquant aux utilisateurs que le serveur a été démarré. [19659014] Le langage de requête graphique est fortement typé et c'est de là que vient l'essentiel de sa fonction de documentation automatique. Ce typage fort est obtenu en utilisant le langage de définition GraphQL Schema . C'est aussi ce qui est utilisé pour spécifier les données résolues par les opérations de requête, de mutation et d'abonnement.

Un exemple pratique de ceci est notre définition de schéma pour notre application de téléchargement ci-dessous.

 const {gql} = require ('apollo -serveur-express ')

const typeDefinitions = gql`
  type File {
    nom de fichier: String!
    mimetype: String!
    encodage: String!
  }

  type User {
     nom d'utilisateur: String
     imageurl: Chaîne
  }

  type Requête {
    getUser: Utilisateur
  }

  type Mutation {
    Créer un utilisateur (
      nom d'utilisateur: String!
      image: Téléchargez!
     ): Utilisateur

    deleteUser (): Booléen!
   }
»
Exporter le type par défautDéfinitions

Ci-dessus, nous avons créé un schéma en utilisant gql composé de trois types; les types File et User qui sont des types d'objet dans le langage de définition de schéma GraphQL et les types Query et Mutation respectivement

Le type d'objet File créé contient trois champs de chaîne; nom de fichier, type MIME et codage qui sont tous généralement contenus dans tout fichier téléchargé. Ensuite, nous avons créé un type d'objet pour les utilisateurs avec deux champs de chaîne; nom d'utilisateur et imageurl . Le champ username est le nom d'utilisateur saisi par un utilisateur lors de la création d'un compte, tandis que imageu rl est l'URL de l'image téléchargée sur Google Cloud Storage. Il serait utilisé passé dans l'attribut image src pour rendre l'image stockée à l'utilisateur.

Ensuite, nous créons le type de requête qui définit la fonction de résolution de requête que nous avons dans l'application. Dans notre cas, il s'agit d'une seule requête utilisée pour obtenir les données de l'utilisateur. La requête getUser renvoie ici toutes les données du type d'objet User.

Nous avons également créé le type Mutation, qui définit ci-dessous les deux fonctions de résolution de mutations suivantes:

  • La première createUser prend un nom d'utilisateur qui est un type scalaire de chaîne et un type d'entrée Upload qui provient de React-Apollo. Il renvoie toutes les données contenues dans le type d'objet Utilisateur après une création de compte réussie
  • Le second deleteUser ne prend aucun argument mais renvoie une valeur booléenne pour indiquer si la suppression a réussi ou non.

Remarque : Le point d'exclamation (! ) attaché à ces valeurs les rend obligatoires, ce qui signifie que les données doivent être présentes dans cette opération.

Implémentation des fonctions de résolution

Après avoir écrit un schéma qui définit la fonction résolveur dans notre application, nous pouvons maintenant aller de l'avant dans l'implémentation des fonctions pour les résolveurs que nous avons précédemment définis dans le schéma.

Nous commençons par la fonction résolveur getUser qui renvoie les données de l'utilisateur.

 // stocke nos données d'utilisateur
let Data = []

export const Queries = {
   getUser: () => {
      retourner les données
  }
}

Nous avons créé un tableau de données qui stocke les données de l’utilisateur. Ce tableau de données doit être utilisé à la fois par la fonction de mutation et de requête et il est donc déclaré globalement.
Ensuite, nous avons implémenté la fonction getUser qui renvoie le tableau contenant les données de l'utilisateur lorsqu'il est interrogé.

Mutating Data

Dans les applications Graphql, les opérations CREATE, UPDATE et DELETE sont effectuées via l'utilisation de la mutation fonctions de résolveur, ce sont mutant les données.

Un exemple de ces résolveurs de mutation sont les deux résolveurs de notre application qui créent un utilisateur et suppriment un utilisateur.

 export const Mutations = {
    createUser: (_, {nom d'utilisateur, image}) => {
      # fonction de résolveur passe-partout
   },

 # réinitialise les données de l'utilisateur
  deleteUser: (_) => {
    Données = []

    if (Data.length 

Voici une explication des deux résolveurs ci-dessus;

  • createUser
    Cela crée un utilisateur utilisant les arguments passés. Premièrement, nous spécifions le parent argument ( _ ) et ensuite nous déstructurons le nom d'utilisateur et l'image qui seraient transmis lors de la mutation dans notre application frontend.
    C'est là que le téléchargement des fichiers aura lieu. Nous reviendra sur l'implémentation réelle de ce résolveur de mutation après avoir configuré une connexion à Google Cloud Storage.
  • deleteUser
    Tel que nous l'avons défini dans notre schéma, cette fonction de résolveur ne prend aucun argument . Le but est de vider le tableau de données et en vérifiant la longueur, il renvoie une valeur booléenne; – true si les éléments sont inférieurs à 1, ce qui signifie que le tableau est vide et ] false sinon.
    Note : Si nous avions une vraie base de données e connexion, cette fonction de résolution prendrait un argument ID qui serait utilisé pour sélectionner l'utilisateur dont l'enregistrement doit être supprimé.

Après avoir créé nos fonctions de schéma et de résolution, nous pouvons maintenant démarrer notre serveur de nœuds et le tester en faisant Requêtes HTTP utilisant curl à http: // localhost: 4000 / graphql ou plus commodément, en utilisant la console Web GraphiQL hors ligne sur http: // localhost: 4000 / graphql comme indiqué ci-dessous;

 La console graphiql avec une requête getUser en cours de réponse vide
Editeur d'interface graphique GraphiQL hors ligne pour effectuer des opérations GraphQL ( Grand aperçu )

Configuration de Google Cloud Storage

Le Google Cloud Storage, un service de stockage de fichiers en ligne est utilisé pour stocker les données d'objets. Il est suffisamment flexible pour répondre aux besoins des applications d'entreprise ou des projets personnels comme celui-ci. Faisant partie des offres de la Google Cloud Platform, elle se trouve dans la section Storage de Google Cloud Console.

Pour commencer, procédez comme suit: [19659051] Visitez la Google Cloud Platform pour créer un compte et un projet .
( Les nouveaux utilisateurs reçoivent 300 $ de crédits GCP, ce qui est largement suffisant pour ce projet de démonstration.)

  • Visitez la section Storage Browser dans Google Cloud Console et cliquez sur le bouton Create Bucket dans le volet de navigation supérieur. [19659007] Entrez un nom de bucket préféré, laissez les autres paramètres par défaut et cliquez sur le bouton Créer au bas de la liste.
  • Après avoir été créé, nous serions redirigés vers le bucket vide similaire à celui ci-dessous;

     La valeur par défaut page des nouveaux buckets créés sur Google Cloud
    Page Web de notre bucket cloud avec dans le navigateur Google Storage ( Grand aperçu )

    À ce stade, nous avons créé un bucket dans lequel les fichiers téléchargés seraient stockés. Ensuite, nous avons besoin d'un Compte de service pour permettre une communication entre notre serveur Node et Google Cloud.

    Que sont les comptes de service?

    Les comptes de service sont d'un type spécial de compte sur Google Cloud, créé pour une interaction non humaine, c'est-à-dire une communication via des API. Dans notre application, il serait utilisé avec une clé de compte de service par notre API pour s'authentifier auprès de Google Cloud lors du téléchargement des images de l'utilisateur stockées.

    Nous suivons les étapes suivantes pour créer un compte de service.

    1. Ouvrez le Section Identity Access Management (IAM) de la console Google Cloud
    2. Dans la barre de navigation de gauche, cliquez sur Comptes de service, puis cliquez sur le bouton Créer un compte de service.
    3. Entrez un nom préféré et une description et cliquez sur le bouton Créer .
      Nous verrions un ID de compte de service généré automatiquement à l'aide des caractères de notre nom saisi.
    4. Ensuite, cliquez sur le menu déroulant Sélectionner un rôle pour sélectionner un rôle pour ce compte de service.
    5. Tapez "Stockage Admin »et cliquez sur le rôle Administrateur de stockage .
      Ce rôle donne à notre serveur Node un contrôle total sur les ressources stockées dans nos compartiments de stockage.
    6. Laissez les champs restants vides et cliquez sur le bouton Terminé.

      Après avoir été créés, nous serions redirigés vers une liste de tous les comptes de service de notre projet, y compris ceux créés par défaut et le compte de service nouvellement créé.

    Ensuite, nous devons créer une clé de compte de service secrète au format JSON. Les étapes suivantes expliquent comment procéder:

    1. Cliquez sur le compte de service nouvellement créé pour accéder à la page de ce compte de service.
    2. Faites défiler jusqu'à la section Clés et cliquez sur le menu déroulant Ajouter une clé et cliquez sur l'option Créer une nouvelle clé qui ouvre un modal.
    3. Sélectionnez un format de fichier JSON et cliquez sur le bouton Créer en bas à droite du modal.

    Après en créant cela, la clé serait téléchargée localement sur notre appareil et nous verrions une alerte indiquant à l'utilisateur de garder la clé privée. En effet, il contient des champs sensibles sur notre projet sur Google Cloud Platform.
    Voici un exemple des champs contenus:

      {
      "type": "service_account",
      "project_id": "PROJECT_NAME-PROJECT_ID",
      "private_key_id": "XXX-XXX-XXX-XXX-XXXX-XXX",
      "private_key": UNE CLÉ R.S.A,
      "client_email": "SERVICE_ACCOUNT_NAME-PROJECT-NAME.iam.gserviceaccount.com",
      "client_id": PROJECT-CLIENT-ID,
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://oauth2.googleapis.com/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
      "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-NAME%PROJECT-NAME-PROJECT-ID.iam.gserviceaccount.com"
    }
    

    Nous sommes maintenant partis avec les étapes supplémentaires suivantes ci-dessous afin de terminer la configuration de notre projet sur Google Cloud Platform.

    1. Déplacez le fichier renommé dans notre répertoire de projet
    2. Ajoutez le nom de ce fichier dans notre ] .gitignore afin de l'empêcher d'être poussé vers Github ou tout autre service de contrôle de version préféré.

    Implémentation de Create User Mutation

    À ce stade, nous pouvons commencer notre implémentation de createUser résolveur en connectant Google Cloud Storage à l'aide du package @ google-cloud / storage . Outre l'utilisation de cette bibliothèque, nous avons la possibilité d'interagir avec Google Cloud Storage en effectuant directement requêtes HTTP vers les API Endpoints disponibles, mais le Google Storage Package fait cela en interne et plus encore pour nous.

    Nous commençons par lancer un processus de connexion avec Google Cloud Storage dans le createUser resolver

     import {Storage} depuis '@ google-cloud / storage';
     
    
    export const Mutations = {
    
    createUser: (_, {nom d'utilisateur, image}) => {
    const bucketName = "node-graphql-application"; // le nom de notre bucket
    
    // Nous transmettons la CLE SECRET téléchargée depuis notre compte de service,
     const storage = new Storage ({keyFilename: path.join (__ dirname, "../upload.json")});
      }
    }
    

    Après avoir initialisé l'importation du constructeur de stockage à partir du package @ google-cloud / storage en utilisant le chemin nous construisons le chemin d'accès au fichier où le fichier json de clé secrète a été stocké. Le fichier de clé secrète contient toutes les données nécessaires pour s'authentifier auprès de Google Cloud.

    Ensuite, nous développons notre fonction de résolution createUser pour traiter et télécharger les images transmises dans notre Bucket sur Google Cloud Storage.

     const removeWhiteSpaces = (name) => {
      return name.replace (/  s + / g, "");
    };
    
    export const Mutations = {
      createUser: async (_, {nom de fichier, image}) => {
       const {filename, createReadStream} = attendre l'image;
    
        laissez sanitizedName = removeWhiteSpaces (nom de fichier);
        attendre une nouvelle promesse ((résoudre, rejeter) => {
          createReadStream (). pipe (
            espace de rangement
              .bucket (bucketName)
              .file (sanitizedName)
              .createWriteStream ()
              .on ("terminer", () => {
                espace de rangement
                  .bucket (bucketName)
                  .file (sanitizedName)
    
               // rendre le fichier public
                  .rendre publique()
                  .then (() => {
                    Données = [];
    
                // sauvegarde les données de l'utilisateur dans le tableau Data
                    Data.push ({
                      nom d'utilisateur: nom d'utilisateur,
                      imageurl: `https://storage.googleapis.com/$ {bucketName} / $ {sanitizedName}`,
                    });
                    résoudre();
                  })
                  .catch ((e) => {
                    rejeter ((e) => console.log (ʻexec error: $ {e} `));
                  });
              })
          );
        });
      }
    }
    

    Ci-dessus, nous effectuons un téléchargement de fichier du fichier passé à la fonction de résolution. Voici une ventilation progressive de tout ce qui est fait dans le résolveur:

    • Premièrement, nous avons déstructuré de manière asynchrone filename et createReadStream à partir du fichier téléchargé. Nous débarrassons ensuite le nom de fichier déstructuré des espaces. La bibliothèque de stockage essaiera de le faire en remplaçant l'espace blanc par le caractère de pourcentage (% ) et cela conduit à une URL de fichier déformée qui peut également choisir d'ignorer.
    • Ensuite, nous créons une nouvelle promesse et en utilisant Node Streams, nous dirigeons le createReadStream vers le constructeur Google Storage. Nous résolvons cette promesse après un téléchargement de fichier réussi ou le rejetons dans l'état de promesse d'erreur de la méthode makePublic .
    • Nous appelons la méthode du bucket sur la classe de stockage et transmettons le nom de notre bucket de stockage et nous appelons ensuite la méthode du fichier et passons le nom du fichier, puis nous appelons la méthode createWriteStream pour télécharger le fichier.
    • Nous rendons le fichier public, en appelant le ] makePublic après avoir passé le nom du bucket et le nom de fichier du fichier récemment téléchargé.
    • Nous créons un objet des données de l'utilisateur contenant le nom d'utilisateur et une URL construite du fichier téléchargé dans notre bucket de stockage. La structure d'URL des fichiers publics sur Google Cloud Storage est https://storage.googleapis.com/ {BUCKET_NAME} / {FILENAME} en utilisant les littéraux de modèle JavaScript, nous pouvons insérer le nom de notre bucket dans BUCKET_NAME espace réservé ainsi que le nom du fichier téléchargé dans l'espace réservé FILENAME ce qui donnerait une URL valide du fichier via laquelle nous pouvons y accéder.

    Remarque : Les fichiers sont privés par défaut sur Google Cloud Storage et ne sont pas accessibles via URL, d'où la nécessité de rendre le fichier public après l'importation dans notre bucket cloud.

    Nous pouvons tester le point de terminaison createUser en utilisant curl pour créer un compte de démonstration.

     curl localhost: 4000 / graphql -F operations = '{"query": "mutation createUser ($ image: Upload! $ username: String!) { createUser (image: $ image username: $ username) {username imageuri}} "," variables ": {" image ": null," username ":" Utilisateur de test "}} '-F map =' {" 0 ": ["variables.image"]} '-F 0 = test.png
    

    Dans la requête HTTP ci-dessus, nous avons spécifié le verbe HTTP en tant que requête POST et notre point de terminaison et autres en-têtes de requête. Après cela, nous avons spécifié l'opération GraphQL pour le résolveur createUser en déduisant le nom d'utilisateur et les types d'image. Ensuite, nous avons spécifié le chemin vers le fichier de test.

    Si la requête ci-dessus aboutit, nous verrons le fichier téléchargé répertorié dans notre bucket comme ceci:

     Requête HTTP pour tester la fonction de résolution de mutation `createUser`
    Requête HTTP effectuée à l'aide de curl pour télécharger une image et l'image téléchargée répertoriée dans le Google Cloud Bucket ( Grand aperçu )

    Consommation de notre API GraphQL

    Il ne nous reste plus qu'à créer le front-end partie de notre application qui consomme notre API GraphQL. Nous amorcerions notre application React en utilisant le create-react-app cli .

    Pour commencer, exécutez les commandes suivantes depuis votre terminal:

     # Create A New Application using Create-React- CLI de l'application
    npx create-react-app Graphql-upload-frontend
    
    # Déplacer dans le répertoire de projet nouvellement créé
    cd Graphql-upload-frontend
    
    # Dépendances nécessaires pour notre application
    yarn add react-dropzone @ apollo / react-hooks graphql apollo-cache-inmemory
    

    Ensuite, nous créons un lien vers notre point de terminaison GraphQL et lançons le client Apollo dans un fichier de configuration séparé.

     // config.js
    
    importer {ApolloClient} depuis "apollo-client";
    import {InMemoryCache} de "apollo-cache-inmemory";
    import {createUploadLink} depuis "apollo-upload-client";
    
    const GRAPHQL_ENDPOINT = "http: // localhost: 3000 / graphql";
    cache const = nouveau InMemoryCache ()
    
    lien const = createUploadLink ({
      URL: GRAPHQL_ENDPOINT,
    });
    
    export const Config = new ApolloClient ({
      lien: uploadLink,
      cache
    })
    

    Si vous avez parcouru la section Getting Started de la documentation React-Apollo, vous remarquerez une légère différence dans les packages utilisés. Voici un aperçu de ce que nous avons accompli ci-dessus:

    • En initialisant le constructeur InMemoryCache du [apollo-cache-inmemor] (https://www.npmjs.com/package/apollo-cache-inmemory) [19659106] y nous avons créé un magasin de données qui stocke le cache de toutes les requêtes effectuées dans notre application
    • Nous avons créé un lien de connexion en utilisant le package apollo-upload-client qui a notre unique GraphQL endpoint en tant que valeur. Ce lien gère les demandes de téléchargement en plusieurs parties qui sont effectuées lorsqu'un fichier est en cours de téléchargement via un point de terminaison GraphQL et gère également l'opération de requête et de mutation.
    • Nous avons initialisé le constructeur du client Apollo dans une variable, passée dans le lien de téléchargement et le cache, puis exporté la variable à utiliser par le fournisseur ApolloClient.

    Nous enveloppons ensuite l'ensemble de l'arborescence des applications avec le ApolloProvider afin que nous puissions effectuer une requête, une mutation ou un abonnement à partir de n'importe quel composant

     import React de "react";
    importer ReactDOM depuis "react-dom";
    importer l'application depuis "./App";
    importer * en tant que serviceWorker depuis "./serviceWorker";
    import {Config} de "./config";
    import {ApolloProvider} depuis "@ apollo / react-hooks";
    
    ReactDOM.render (
        
          
        ,
      document.getElementById ("racine")
    );
    
    serviceWorker.unregister ();
    

    Nous pouvons voir ci-dessus le ApolloProvider encapsuler le composant racine et nous avons passé le client Apollo qui a été exporté du fichier de configuration sous Config dans le client d'ApolloProvider.

    Travailler avec des données GraphQL

    À ce stade, notre application est presque prête à commencer à travailler avec les données de l'application GraphQL mais avant cela, nous devons définir nos opérations GraphQL. Vous vous souvenez de la fonctionnalité de frappe puissante de GraphQL dont nous avons parlé précédemment? Cela s'applique également côté client.

    Nous définissons nos opérations GraphQL en utilisant gql du package @ apollo / react-hooks . Nous utilisons gql avec des accents graves (backticks) pour analyser une chaîne GraphQL. Nous définissons d'abord le type d'opération (soit une mutation, un abonnement ou une requête) puis nous lui donnons un nom. Si l'opération prend des arguments, nous déduisons les types des arguments individuels entre parenthèses à un identifiant de préfixe en utilisant un opérateur sigil ($) et nous pouvons ensuite utiliser cet argument typé via son préfixe.

    Nous pouvons en voir un exemple pratique dans les trois opérations GraphQL que nous avons définies ci-dessous pour notre application.

     # data.js
    import {gql} depuis "@ apollo / react-hooks";
    
    export const CREATE_USER = gql`
      mutation createUser ($ username: String !, $ image: Upload!) {
        createUser (nom d'utilisateur: $ username, image: $ image) {
          Nom d'utilisateur
        }
      }
    `;
    
    export const DELETE_ACCOUNT = gql`
      mutation deleteAccount {
        Supprimer l'utilisateur
      }
    `;
    
    export const GET_USER = gql`
      query getUser {
        getUser {
          Nom d'utilisateur
          URL de l'image
        }
      }
    `;
    

    Ci-dessus, nous définissons nos opérations GraphQL à utiliser dans les variables et nous exportons ces variables afin qu'elles puissent être utilisées par les composants de l'application. Voici un bref aperçu de chaque variable:

    • CREATE_USER
      Il définit la mutation createUser qui reçoit un nom d'utilisateur de type chaîne et également une image qui a le type d'objet Upload de React -Apollon. L'image représente le fichier qui est téléchargé par l'utilisateur avec tous les champs nécessaires à l'intérieur.
    • DELETE_ACCOUNT
      Ceci est également défini comme une mutation, mais il ne reçoit rien donc il n'a pas de parenthèses contenant un scalaire défini. Il définit et nomme uniquement la mutation deleteUser .
    • GET_USER
      Ceci est défini comme une opération de requête. Nous pouvons voir que les deux valeurs renvoyées par cette requête sont indiquées dans les accolades. Bien que cette requête ne reçoive aucun argument, les requêtes GraphQL reçoivent parfois aussi des arguments lors de la récupération d'une donnée spécifique et les arguments sont également définis entre parenthèses comme une mutation.

    Maintenant que nous avons une connexion GraphQL dans notre application, nous pouvons maintenant construire out Application Layout où nous utilisons les opérations GraphQL précédemment définies en deux composants.

    Application Layout

    Notre application aurait les états suivants pour accueillir un nouvel utilisateur, créer un compte et enfin garder cet utilisateur connecté

    • État de l'invité
      Il s'agit de l'état initial de l'application où les utilisateurs voient un nom d'utilisateur et une image par défaut. Un utilisateur peut changer d’état en créant un compte.
    • Créer l’état du compte
      À ce stade, les utilisateurs peuvent taper un nom d’utilisateur et faire glisser «n» déposer ou cliquer pour ajouter une image. C'est le point où la mutation createUser est déclenchée lorsque le bouton d'envoi est cliqué.
    • État de connexion
      À ce stade, un compte a été créé, l'image affichée est celle qui a été téléchargée par l'utilisateur et est accessible à l'aide de l'url d'image de Google Cloud Bucket.

    Tous les états seraient implémentés en deux composants: App Component et Create Account Component . Ces états seraient gérés à l'aide de React Hooks.

    Nous commençons par implémenter l'état Invité dans le App Component qui affiche un texte de bienvenue et une image stockée par défaut.

     import React, {useState} de "réagir";
    
    const App = () => {
     const [ isCreatingAccount , setCreatingAccount ] = useState (faux)
    
     revenir (
      
    {isCreatingAccount (true)}} className = "auth">

    Se connecter

    <div className = "contenu"  utilisateur et utilisateur par défaut

    Bonjour, je suis Groot

    Vous pouvez vous connecter pour devenir vous!

    ) } exporter l'application par défaut

    Ci-dessus, nous avons un composant React qui rend; un bouton, une image et un texte d'accueil par défaut. Un utilisateur peut changer l'état de l'application pour créer un compte en cliquant sur le bouton Se connecter.

    Lorsqu'il est placé dans le fichier app.js de notre projet, notre application devient similaire à l'application ci-dessous;

     État de l'application par défaut
    L'état par défaut de l'application à l'ouverture ( Grand aperçu )

    Nous développons le composant App pour passer de la vue par défaut aux champs de saisie en un clic de Bouton Créer un compte .

     import React, {useState, useEffect} de "react";
    import {useMutation, useLazyQuery} depuis "@ apollo / react-hooks";
    importer CreateUser depuis "./create-user";
    import "../App.css";
    importer {DELETE_ACCOUNT, GET_USER} depuis "../data";
    
    function App () {
      const [deleteUser] = useMutation (DELETE_ACCOUNT);
      const [getUser, { data, error }] = useLazyQuery (GET_USER);
    
      // état utilisé pour basculer entre un invité et un utilisateur
      const [isLoggedIn, setLoggedIn] = useState (faux);
      const [isCreatingAccount, beginCreatingAccount] = useState (faux);
    
      // données utilisateur stockées dans l'état et transmises à GraphQL
      const [userName, setuserName] = useState ("");
      const [imgUrl, setImgUrl] = useState (null);
    
      // fonction deleteAccount qui supprime le compte de l'utilisateur
      const deleteAnAccount = () => {
        Supprimer l'utilisateur()
          .then (() => {
            // réinitialise tout l'état stocké
            setLoggedIn (faux);
            setImgUrl (null);
            setuserName ("");
          })
          .catch ((e) => console.log (e));
      };
    
      useEffect (() => {
        if (isLoggedIn && data! == undefined) {
          setImgUrl (data.getUser [0] .imageurl);
        }
      }, [data]);
    
      revenir (
        
    { if (! isLoggedIn) { beginCreatingAccount (! isCreatingAccount); } else if (isLoggedIn) { deleteAnAccount (); } }} className = "auth" >

    {! isLoggedIn? (! isCreatingAccount? "Connexion": "Annuler"): "Déconnexion"}

    {! IsCreatingAccount? (
     utilisateur et utilisateur par défaut

    Bonjour, je suis {userName.length> 3? `$ {userName}`: `Groot`}.

    {! IsLoggedIn ? "Vous pouvez vous connecter pour devenir vous!" : "Vous vous déconnectez pour devenir Groot!"}

    ): ( { getUser (); setLoggedIn (vrai); beginCreatingAccount (false); }} /> )}
    ); } exporter l'application par défaut;

    Dans le code ci-dessus, nous avons apporté les ajouts suivants à notre application:

    • Nous avons créé deux nouveaux états pour suivre quand l'utilisateur est connecté et quand l'utilisateur crée un compte. Ces deux états sont mis à jour par le bouton Se connecter qui peut maintenant démarrer un processus de création de compte ou l'annuler et revenir à l'état par défaut.
    • Notre application utilise maintenant le hook useLazyQuery qui vient du package apollo / react-hooks pour effectuer une requête GraphQL pour récupérer les données de l'utilisateur en utilisant notre définition GET_USER précédemment créée.

      • Notre requête ici est dite paresseuse car elle est pas exécuté immédiatement l'application est chargée. Il est exécuté une fois que la mutation createUser du composant Créer un compte a été exécutée avec succès. Selon la React – documentation Apollo useLazyQuery n'exécute pas la requête associée immédiatement, mais plutôt en réponse aux événements.
    • Nous surveillons la valeur des données déstructurées qui n'est pas définie par défaut jusqu'à ce que la requête soit effectuée, dans un useEffect puis nous basculons l'attribut image src sur l'imageurl renvoyé par la requête après avoir interrogé les données de l'utilisateur.

    • En cliquant sur le bouton de connexion, l'état isCreatingAccount est mis à jour sur true et le composant Créer un compte s'affiche pour qu'un utilisateur saisisse un nom d'utilisateur et ajoute un fichier image.

    • Après avoir créé un compte, un utilisateur peut cliquer sur le bouton Déconnexion pour appeler la fonction deleteAUser qui exécute la mutation deleteUser et, en cas de succès, réinitialise tous les états in the App Component.

    Now, we can implement a drag ‘n’ drop functionality within the create-user component where an image can be dragged or clicked to open the device media explorer and after this we upload the added file to our Node server.

    import React, { useState, useCallback } from "react";
    import { useMutation } from "@apollo/react-hooks";
    import { useDropzone } from "react-dropzone";
    import "../App.css";
    import { CREATE_USER, GET_USER } from "../data";
    
    const CreateUser = (props) => {
      const { updateProfile } = props;
      const [createAccount, { loading }] = useMutation(CREATE_USER);
      // user data stored in state and passed to GraphQL
      const [userName, setuserName] = useState("");
      // user's uploaded image store in useState and passed to the GraphQL mutation
      const [userImage, setUserImage] = useState(null);
    
      // create user mutation function fired at the click of `createAccount` button
      const createAUser = () => {
        createAccount({
          variables: {
            username: userName,
            image: userImage,
          },
        })
          .then(() => {
            updateProfile();
          })
          .catch((e) => console.log(e));
      };
    
      const onDrop = useCallback(([file]) => {
        setUserImage(file);
      }, []);
    
      const {
        getRootProps,
        isDragActive,
        isDragAccept,
        getInputProps,
        isDragReject,
      } = useDropzone({
        onDrop,
        accept: "image/jpeg , image/jpg, image/png",
      });
    
      return (
        

    {!loading ? "Create An Account" : "Creating Account ..."}



    setuserName(e.target.value)} placeholder="some nifty name" required={true} type="text" />

    {!userImage ? (

    {!isDragActive ? `Tap or Drag 'n' Drop Image to Add Profile Picture` : isDragReject ? "Ooops upload images only" : "Drop your image here to upload"}

    ) : (
    image illustration

    {userImage.path}

    )}

    <button style={{ background: userName.length < 3 && "transparent", color: userName.length < 3 && "silver", }} className="create-acct-btn" onClick={(e) => { e.preventDefault(); createAUser(); }} disabled={userName.length < 3 || loading} > {!loading ? "Create Account" : "Creating Account"}
    ); }; export default CreateUser;

    Here is a gradual breakdown of all that’s happening above:

    • We destructured createAccount resolver function from the useMutation hook after passing in our previously defined CREATE_USER operation.
    • We created a function;- createAUser which is invoked at the click of the Create Account button after typing in a username and adding an image.
    • We created an onDrop function which is wrapped in the useCallback to avoid a recompution of this function. After the file is dropped, we keep it temporarily in the userImage state to be used when submitting the data.
    • We destructured the four root properties from the useDropZone hook and then specified the acceptable file types alongside our custom onDrop function.
    • Next, those root properties destructured are used in building a reactive dropzone, that reacts when an acceptable file or non – acceptable file is being dragged over our dropzone. This done by applying the root properties to our selected dropzone , which here happens to be a div element wrapping other smaller div elements. Also, by spreading the …getInputProps() in the input element, it makes the input element hidden with a file type so when the dropzone is clicked, it opens the device media explorer.
    • Lastly, we used the ternary operator in the inline styles to make the div have a border when a file is being dragged over it and also make this border red when a file type not specified is being dragged.
    Testing the isDragAccept and isDragReject applied props
    Testing the reactiveness of the dropzone using the isDragAccept and isDragReject props from react-dropzone (Large preview)

    Now at the click of the Create Account button, using a ternary operator and the loading boolean value destructured from the useMutation hook, we switch the “Create Account “ text to “Creating Account …” to indicate that the data is being submitted and a network request is in flight.

    A test of the entire upload application built
    An upload to test the entire application built (Large preview)

    Once the mutation has been executed successfully, we execute the lazy getUser query and we switch back to the Home Component but this time with data from the getUser query. Using the imageurl value returned in the getUser query result, we can access the uploaded image over the internet and also display it in the page.

    CreateUser mutation being executed
    Application view changed to reflect a loading mutation state (Large preview)

    Conclusion

    In this article, we have walked through three aspects of creating a file upload pipeline. First we built a frontend application where users can drag and upload a file to upload it. Then we built a GraphQL API that connects the frontend application and a mutation to handle the incoming file. Lastly we connected our server to the Google Cloud Storage to store the file from the node server.

    It is also recommended to read Apollo Server File Upload Best Practices on two more ways of performing file in a GraphQL application.

    All files and code snippets referenced and used within this article is available Github.

    References

    Smashing Editorial(ks, ra, il)




    Source link

    Revenir vers le haut