Catégories
Plugin et site web

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

A propos de l'auteur

Nwani Victory travaille en tant qu'ingénieur Frontend chez Liferithms.inc de Lagos, au Nigeria. Après les heures de bureau, il se double d'un ingénieur cloud à la recherche de moyens de faire du cloud…
Plus à propos
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 tirant parti de React-Apollo, cet article se concentre sur la façon dont une fonctionnalité de téléchargement de fichier peut être ajoutée à une application frontale nouvelle ou existante 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 Google Cloud.
  • Configurer une connexion à Google Cloud Storage.
  • Collecter les entrées de fichiers dans une application React et les envoyer à une application backend GraphQL à l'aide de React Apollo.

Remarque: Bien que tous les extraits de code soient expliqués, pour bien les comprendre, vous devez comprendre 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 le téléchargement de fichiers 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.

Configurer une API Node GraphQL

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

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

# Create a new Project folder and( && ) move into it
mkdir Node-GraphQL-API && cd Node-GraphQL-API

# Create a new Node project
yarn init -y

# Install the two needed dependencies 
yarn add apollo-server-express express

Ensuite, nous procédons à la construction d'un seul point de terminaison GraphQL, accessible via le port 4000.

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

const { Queries , Mutations , TypeDefs } = require('./resolvers') 

const resolvers = {
  Query : Queries , 
  Mutation : Mutations 
} 

const server = new ApolloServer({ TypeDefs, resolvers });
 
const app = express();
server.applyMiddleware({ app });
 
app.listen({ port: 4000 }, () =>
  console.log(`Graphiql running at http://localhost:4000/${server.graphqlPath}`));

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

Ensuite, nous avons créé une instance de express.js dans la variable d'application et l'avons intégrée au serveur apollo en appelant le applyMiddleware méthode. 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é le listen sur l'instance express, en lui disant 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é.

Le langage de requête graphique est fortement typé et c'est de là que vient la plupart de sa fonction de documentation automatique. Ce typage fort est obtenu à l'aide du langage de définition de schéma GraphQL. C'est aussi ce qui est utilisé pour spécifier les données résolues par les opérations Query, Mutation et Subscription.

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-server-express')

const typeDefinitions  = gql` 
  type File {
    filename: String!
    mimetype: String!
    encoding: String!
  }

  type User {
     username: String
     imageurl: String
  }

  type Query { 
    getUser  : User
  }

  type Mutation {
    createUser ( 
      username : String!
      image : Upload!
     ) : User

    deleteUser () : Boolean!
   }
`
export default typeDefinitions

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

Le type d'objet File créé contient trois champs de chaîne; filename, mimetype and encoding qui sont tous généralement contenus dans n'importe quel fichier téléchargé. Ensuite, nous avons créé un type d'objet pour les utilisateurs avec deux champs de chaîne; username et imageurl. le username est le nom d'utilisateur saisi par un utilisateur lors de la création d'un compte, tandis que le imageurl est l'url de l'image téléchargée sur Google Cloud Storage. Il serait utilisé passé dans l'image src attribut 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. le getUser La requête ici renvoie toutes les données du type d'objet Utilisateur.

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

  • Le premier createUser prend un nom d'utilisateur qui est un type scalaire 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 deuxième 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és à ces valeurs les rendent 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 le getUser fonction résolveur qui renvoie les données de l’utilisateur.

// stores our user data
let Data  = ()

export const Queries = {
   getUser: () => {
      return Data
  }
}

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é le getUser fonction qui renvoie le tableau contenant les données de l’utilisateur lorsqu’il est interrogé.

Mutation des données

Dans les applications Graphql, les opérations CREATE, UPDATE et DELETE sont effectuées via l'utilisation des fonctions de résolution de mutation, ce sont subir une mutation 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: (_, { username, image }) => {
      # boilerplate resolver function
   },

 # resets the user's data 
  deleteUser: (_ ) =>  {
    Data = ()

    if (Data.length 

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

  • createUser
    Cela crée un utilisateur utilisant les arguments passés. Tout d'abord, nous spécifions l'argument parent (_) et ensuite nous déstructurons le nom d'utilisateur et l'image qui seraient passés lors de la mutation dans notre application frontend.
    C'est là que le téléchargement des fichiers aura lieu. Nous reviendrons sur la mise en œuvre réelle de ce résolveur de mutation après avoir mis en place une connexion au Google Cloud Storage.
  • deleteUser
    Telle que nous l'avons définie dans notre schéma, cette fonction de résolution 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 si non.
    Remarque: Si nous avions une vraie connexion à la base de données, 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 effectuant des requêtes HTTP à l'aide de curl à http://localhost:4000/graphql ou plus facilement, 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 étant une réponse vide
Éditeur 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 plate-forme Google Cloud, il se trouve dans le Espace de rangement section de la console Google Cloud.

Pour commencer, suivez les étapes suivantes:

  1. Accédez à Google Cloud Platform pour créer un compte et un projet.
    (Les nouveaux utilisateurs reçoivent 300 $ de crédits GCP, ce qui est plus que suffisant pour ce projet de démonstration.)
  2. Accédez à la section Navigateur de stockage, dans la console Google Cloud, puis cliquez sur le bouton Créer un bucket dans le volet de navigation supérieur.
  3. Entrez un nom de compartiment préféré, laissez les autres paramètres par défaut et cliquez sur le bouton Créer en bas de la liste.

Après avoir été créés, nous serions redirigés vers le seau vide similaire à celui ci-dessous;

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

À ce stade, nous avons créé un compartiment 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 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éversement des images des utilisateurs stockés.

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

  1. Ouvrez la section Identity Access Management (IAM) de la console Google Cloud.
  2. Dans la barre de navigation de gauche, cliquez sur Comptes de service et, une fois sur place, 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 bouton.
    Nous verrions un identifiant de compte de service généré automatiquement à l'aide des caractères de notre nom saisi.
  4. Ensuite, cliquez sur le Sélectionnez un rôle menu déroulant pour sélectionner un rôle pour ce compte de service.
  5. Tapez «Storage Admin» et cliquez sur le rôle Storage Admin.
    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 ci-dessous 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 bouton Ajouter une clé liste déroulante et cliquez sur le Créer une nouvelle clé option 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 avoir créé 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": AN R.S.A KEY,
  "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 d'éviter qu'il ne soit 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 mise en œuvre du createUser résolveur en connectant le 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 des requêtes HTTP directes vers les points de terminaison d'API disponibles, mais le package de stockage Google le fait en interne et plus encore pour nous.

Commençons par lancer un processus de connexion avec Google Cloud Storage dans le createUser résolveur

import  { Storage } from '@google-cloud/storage';
 

export const Mutations = {

createUser : (_, { username, image }) => {
const bucketName = "node-graphql-application"; // our bucket name

// We pass-in the downloaded SECRET KEY from our Service Account, 
 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, à l'aide de path, nous construisons le chemin du fichier vers lequel 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 élargissons notre createUser fonction de résolution 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 (_ , {filename , image}) => {
   const { filename, createReadStream } = await image;

    let sanitizedName = removeWhiteSpaces(filename);
    await new Promise((resolve, reject) => {
      createReadStream().pipe(
        storage
          .bucket(bucketName)
          .file(sanitizedName)
          .createWriteStream()
          .on("finish", () => {
            storage
              .bucket(bucketName)
              .file(sanitizedName)

           // make the file public
              .makePublic() 
              .then(() => {
                Data = ();

            // save user's data into the Data array
                Data.push({
                  username: username,
                  imageurl: `https://storage.googleapis.com/${bucketName}/${sanitizedName}`,
                });
                resolve();
              })
              .catch((e) => {
                reject((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 canalisons le createReadStream au 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 la promesse d'erreur du makePublic méthode.
  • Nous appelons la méthode the bucket sur la classe de stockage et transmettons le nom de notre bucket de stockage et nous appelons en outre la méthode file et passons le nom du fichier, puis nous appelons le createWriteStream méthode pour télécharger le fichier.
  • Nous rendons le fichier public, en appelant le makePublic après avoir transmis le nom du compartiment 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}, à l'aide des littéraux de modèle JavaScript, nous pouvons insérer le nom de notre bucket dans le BUCKET_NAME espace réservé et également le nom du fichier téléchargé dans le FILENAME placeholder et cela donnerait une URL valide du fichier à 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 une URL, d'où la nécessité de rendre le fichier public après le téléchargement dans notre bucket cloud.

Nous pouvons tester le createUser endpoint utilisant curl pour effectuer une création de 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" : "Test user" } }' -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 d'autres en-têtes de requête. Après cela, nous avons spécifié l'opération GraphQL pour le createUser résolveur, en déduisant le nom d'utilisateur et les types d'images. Ensuite, nous avons spécifié le chemin d'accès au fichier de test.

Si la demande ci-dessus aboutit, nous verrons le fichier téléchargé répertorié dans notre bucket comme suit:

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 Google Cloud Bucket (grand aperçu)

Consommer notre API GraphQL

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

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

# Create A New Application using Create-React-App CLI
npx create-react-app Graphql-upload-frontend

# Move into newly created project directory
cd Graphql-upload-frontend

# Dependencies needed for our 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 initions le client Apollo dans un fichier de configuration séparé.

// config.js

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createUploadLink } from "apollo-upload-client";

const GRAPHQL_ENDPOINT = "http://localhost:3000/graphql"; 
const cache = new InMemoryCache()

const Link = createUploadLink({
  url: GRAPHQL_ENDPOINT,
});

export const Config = new ApolloClient({
  link: uploadLink,
  cache
})

Si vous avez parcouru la section Mise en route 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 InMemoryCache constructeur du (apollo-cache-inmemor)(https://www.npmjs.com/package/apollo-cache-inmemory)y package, nous avons créé un magasin de données qui stocke le cache de toutes les demandes effectuées dans notre application
  • Nous avons créé un lien de connexion à l'aide du apollo-upload-client package qui a notre seul point de terminaison GraphQL comme 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 Apollo Client dans une variable, passé le lien de téléchargement et le cache, puis exporté la variable à utiliser par le fournisseur ApolloClient.

Nous enveloppons ensuite l'intégralité de notre arbre d'application avec le ApolloProvider, afin que nous puissions faire une requête, une mutation ou un abonnement à partir de n'importe quel composant.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Config } from "./config";
import { ApolloProvider } from "@apollo/react-hooks";

ReactDOM.render(
    
      
    ,
  document.getElementById("root")
);

serviceWorker.unregister();

On peut voir ci-dessus le ApolloProvider envelopper le composant racine et nous avons passé dans le client Apollo qui a été exporté à partir du fichier de configuration comme Config dans l'accessoire 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? Il s'applique également du côté client.

Nous définissons nos opérations GraphQL en utilisant gql du @apollo/react-hooks paquet. 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 à l'aide d'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 } from "@apollo/react-hooks";

export const CREATE_USER = gql`
  mutation createUser($username: String!, $image: Upload!) {
    createUser(username: $username, image: $image) {
      username
    }
  }
`;

export const DELETE_ACCOUNT = gql`
  mutation deleteAccount {
    deleteUser
  }
`;

export const GET_USER = gql`
  query getUser {
    getUser {
      username
      imageurl
    }
  }
`;

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 le createUser mutation qui reçoit un nom d'utilisateur de type chaîne et également une image qui a le type d'objet Upload de React-Apollo. L'image représente le fichier téléchargé par l'utilisateur avec tous les champs nécessaires.
  • DELETE_ACCOUNT
    Ceci est également défini comme une mutation, mais il ne reçoit rien donc il n'a pas de parenthèse contenant un scalaire défini. Il définit et nomme uniquement le deleteUser mutation.
  • 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 également 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 une mise en page d'application où nous utilisons les opérations GraphQL précédemment définies dans deux composants.

Disposition de l'application

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 cet état en créant un compte.
  • Créer l'état du compte
    À ce stade, les utilisateurs peuvent saisir 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 mis en œuvre en deux composants: Composant d'application et Créer un composant de compte. Ces états seraient gérés à l'aide de React Hooks.

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

import React, {useState} de "react";

const App = () => {
 const (isCreatingAccount, setCreatingAccount) = useState (faux)

 revenir (
  
{isCreatingAccount (true)}} className = "auth">

Se connecter

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

Salut, 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 app.js fichier dans 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 lors de 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 Créer un compte bouton.

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, {données, erreur}) = useLazyQuery (GET_USER);

  // état utilisé pour basculer entre un invité et un utilisateur
  const (isLoggedIn, setLoggedIn) = useState (false);
  const (isCreatingAccount, beginCreatingAccount) = useState (false);

  // 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);
    }
  }, (Les données));

  revenir (
    
{ if (! isLoggedIn) { beginCreatingAccount (! isCreatingAccount); } else if (isLoggedIn) { deleteAnAccount (); } }} className = "auth" >

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

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

Salut, 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 le moment où l'utilisateur est connecté et le moment où 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 désormais le useLazyQuery crochet qui vient de apollo/react-hooks package pour effectuer une requête GraphQL pour récupérer les données de l'utilisateur à l'aide de notre GET_USER définition.

    • Notre requête ici est dite paresseuse car elle n'est pas exécutée immédiatement après le chargement de l'application. Il est exécuté après le createUser la mutation dans le composant Créer un compte a été exécutée avec succès. Selon la documentation React – Apollo, useLazyQuery n'exécute pas la requête associée immédiatement, mais plutôt en réponse à des é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 faite, 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 Connexion, le isCreatingAccount l'état 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 le deleteAUser fonction qui exécute la deleteUser mutation et en cas de succès, il réinitialise tous les états dans le composant d'application.

Maintenant, nous pouvons implémenter une fonctionnalité de glisser-déposer dans le composant create-user où une image peut être glissée ou cliquée pour ouvrir l'explorateur multimédia de l'appareil et après cela, nous téléchargeons le fichier ajouté sur notre serveur Node.

import React, {useState, useCallback} de "react";
import {useMutation} depuis "@ apollo / react-hooks";
importer {useDropzone} depuis "react-dropzone";
import "../App.css";
importer {CREATE_USER, GET_USER} depuis "../data";

const CreateUser = (accessoires) => {
  const {updateProfile} = accessoires;
  const (createAccount, {loading}) = useMutation (CREATE_USER);
  // données utilisateur stockées dans l'état et transmises à GraphQL
  const (userName, setuserName) = useState ("");
  // magasin d'images téléchargé par l'utilisateur dans useState et transmis à la mutation GraphQL
  const (userImage, setUserImage) = useState (null);

  // crée une fonction de mutation utilisateur déclenchée en cliquant sur le bouton `createAccount`
  const createAUser = () => {
    créer un compte({
      variables: {
        nom d'utilisateur: userName,
        image: userImage,
      },
    })
      .then (() => {
        mettre à jour le profil();
      })
      .catch ((e) => console.log (e));
  };

  const onDrop = useCallback (((fichier)) => {
    setUserImage (fichier);
  }, ());

  const {
    getRootProps,
    isDragActive,
    isDragAccept,
    getInputProps,
    isDragReject,
  } = useDropzone ({
    onDrop,
    accepter: "image / jpeg, image / jpg, image / png",
  });

  revenir (
    

{!chargement ? "Créer un compte": "Créer un compte ..."}



setuserName (e.target.value)} placeholder = "un nom astucieux" requis = {true} type = "texte" />

{! userImage? (

{! isDragActive ? `Appuyez ou faites glisser 'n' Déposer l'image pour ajouter une photo de profil` : isDragReject ? "Ooops télécharger des images uniquement" : "Déposez votre image ici pour la téléverser"}

): (
illustration de l'image

{userImage.path}

)}

<bouton style = {{ arrière-plan: userName.length <3 && "transparent", couleur: userName.length < 3 && "silver", }} className="create-acct-btn" onClick={(e) => { e.preventDefault (); createAUser (); }} disabled = {userName.length < 3 || loading} > {!chargement ? "Créer un compte": "Créer un compte"}
); }; exporter CreateUser par défaut;

Voici une ventilation progressive de tout ce qui se passe ci-dessus:

  • Nous avons déstructuré createAccount fonction résolveur du useMutation hook après avoir passé notre précédemment défini CREATE_USER opération.
  • Nous avons créé une fonction; – createAUser qui est invoquée au clic du Créer un compte bouton après avoir saisi un nom d'utilisateur et ajouté une image.
  • Nous avons créé un onDrop fonction qui est encapsulée dans useCallback pour éviter une recalcul de cette fonction. Une fois le fichier déposé, nous le conservons temporairement dans le userImage état à utiliser lors de la soumission des données.
  • Nous avons déstructuré les quatre propriétés racine du hook useDropZone, puis spécifié les types de fichiers acceptables à côté de notre fonction onDrop personnalisée.
  • Ensuite, ces propriétés racine déstructurées sont utilisées dans la construction d'une zone de dépôt réactive, qui réagit lorsqu'un fichier acceptable ou non acceptable est glissé sur notre zone de dépôt. Cela se fait en appliquant les propriétés racine à notre zone de dépôt sélectionnée, qui se trouve ici être un élément div enveloppant d'autres éléments div plus petits. Aussi, en diffusant le …getInputProps() dans le input élément, il rend l'élément d'entrée masqué avec un type de fichier.Ainsi, lorsque l'utilisateur clique sur la zone de dépôt, il ouvre l'explorateur de support de périphérique.
  • Enfin, nous avons utilisé l'opérateur ternaire dans les styles en ligne pour faire en sorte que le div ait une bordure lorsqu'un fichier est glissé dessus et également rendre cette bordure rouge lorsqu'un type de fichier non spécifié est glissé.
Test des accessoires appliqués isDragAccept et isDragReject
Test de la réactivité de la zone de dépôt à l'aide des accessoires isDragAccept et isDragReject de react-dropzone (Grand aperçu)

Maintenant, en cliquant sur le bouton Créer un compte, en utilisant un opérateur ternaire et la valeur booléenne de chargement déstructurée du useMutation hook, nous basculons le texte «Créer un compte» sur «Créer un compte…» pour indiquer que les données sont soumises et qu'une demande réseau est en cours.

Un test de l'ensemble de l'application de téléchargement construit
Un téléchargement pour tester l'ensemble de l'application construite (Grand aperçu)

Une fois la mutation exécutée avec succès, nous exécutons le paresseux getUser requête et nous retournons au composant Home mais cette fois avec les données du getUser requete. En utilisant la valeur imageurl renvoyée dans le getUser résultat de la requête, nous pouvons accéder à l'image téléchargée sur Internet et l'afficher également dans la page.

Mutation CreateUser en cours d'exécution
La vue de l'application a été modifiée pour refléter un état de mutation de chargement (Grand aperçu)

Conclusion

Dans cet article, nous avons abordé trois aspects de la création d'un pipeline de téléchargement de fichiers. Nous avons d'abord créé une application frontale dans laquelle les utilisateurs peuvent faire glisser et télécharger un fichier pour le télécharger. Ensuite, nous avons construit une API GraphQL qui connecte l'application frontale et une mutation pour gérer le fichier entrant. Enfin, nous avons connecté notre serveur au Google Cloud Storage pour stocker le fichier du serveur de nœuds.

Il est également recommandé de lire les meilleures pratiques de téléchargement de fichiers Apollo Server sur deux autres façons d'exécuter un fichier dans une application GraphQL.

Tous les fichiers et extraits de code référencés et utilisés dans cet article sont disponibles sur Github.

Les références

Éditorial fracassant(ks, ra, il)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *