Comment créer et déployer Full Stack React application

Bonjour, Habr! Je vous présente la traduction de l'article "Comment construire et déployer une application React Full-Stack" par Frank Zickert.

Les composants d'infrastructure facilitent la création, l'exécution et le déploiement d'une application React à part entière. Avec ces composants, REACT vous pouvez vous concentrer sur l'écriture logique métier de votre application. Vous n'avez pas à vous soucier de sa configuration.

Vous voulez devenir un développeur pile complète? L'application full-stack complète l'interface Web interactive React avec un serveur et une base de données. Mais cette application nécessite beaucoup plus d'options que une simple application d'une page.

Nous utilisons des composants d'infrastructure . Ces composants React nous permettent de définir notre architecture d'infrastructure dans le cadre de notre application React. Nous n'avons plus besoin d'autres paramètres, tels que Webpack, Babel ou Serverless.

Commencer


Vous pouvez configurer votre projet de trois façons:


Une fois que vous avez installé les dépendances (exécutez npm install ), vous pouvez créer le projet avec une seule commande: npm run build .

Le script de construction ajoute trois autres scripts Ă  package.json:

  • npm run{your-project-name} lance votre application React localement (mode hot-dev, sans partie serveur et base de donnĂ©es)
  • npm run start-{your-env-name} name} exĂ©cute l'ensemble de la pile logicielle au niveau local. Remarque: Java 8 JDK est requis pour exĂ©cuter la base de donnĂ©es hors ligne. Voici comment vous pouvez installer le JDK .
  • npm run deploy-{your-env-name} name} dĂ©ploie votre application sur AWS.

Remarque Pour les applications dans les Déployez AWS dont vous avez besoin utilisateur technique IAM avec ces droits. Placez les informations d'identification de l'utilisateur dans votre fichier .env comme suit:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

Définissez l'architecture de votre application


Les projets basés sur des composants d'infrastructure ont une structure claire. Avez-vous un composant de niveau supérieur. Cela définit l'architecture globale de votre application.

Les sous-composants (composants enfants) affinent (étendent) le comportement de l'application et ajoutent des fonctions.

Dans l'exemple suivant, le composant <ServiceOrientedApp /> est notre composant de niveau supérieur. Nous l'exportons en tant que fichier par défaut dans notre fichier de point d'entrée ( src / index.tsx) .

 export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); App' export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); 

<ServiceOrientedApp /> est une application Web interactive. Vous pouvez affiner (étendre) la fonctionnalité de l'application à l'aide que vous fournissez des composants de l'enfant. Il prend en charge les <DataLayer /> <Environment /> , <Route /> , <Service /> et <DataLayer /> .

<Envrionment /> détermine le temps d'exécution de votre application. Par exemple, vous pouvez avoir la version dev et prod. Vous pouvez exécuter et déployer chacun séparément.

<Route /> est la page de votre candidature. cela fonctionne comme <Route /> dans react-router. Voici un didacticiel sur l'utilisation des itinéraires .

<Service /> définit une fonction qui est exécutée sur le côté serveur. Il peut avoir un ou plusieurs <Middleware /> - composants comme les enfants.

<Middleware /> fonctionne comme express.js-middleware.
<DataLayer /> ajoute une base de données NoSQL à votre application. Accepte <Entry /> - composants en tant qu'enfants. <Entrée /> décrit le type d'éléments dans votre base de données.

Ces composants sont tout ce dont nous avons besoin pour créer notre application full-stack. Comme vous pouvez le voir, notre application a: un runtime, une page, deux services et une base de données avec un enregistrement.

la structure du composant fournit une vue claire de votre application. Plus votre application obtient, plus il est important.

Vous avez peut - être remarqué que le <Service /> sont des enfants du <DataLayer /> . Il a une explication simple. Nous voulons que nos services aient accès à la base de données. Il est vraiment aussi simple que cela!

bases de données conception


<DataLayer /> crée Amazon DynamoDB. Cette base de données est une valeur clé (NoSQL). Il fournit de hautes performances à toute échelle. Mais contrairement à la base de données relationnelle, il ne supporte pas les requêtes complexes.

Le schéma de base de données a trois champs: primaryKey , rangeKey et data . Ceci est important parce que vous devez savoir que vous pouvez trouver des enregistrements que pour ses clés. Soit primaryKey , soit rangeKey , ou les deux.

Avec cette connaissance, nous allons jeter un oeil Ă  notre <Entry /> :

 export const USER_ENTRY_ID = "user_entry"; export default function UserEntry (props) { return <Entry id={ USER_ENTRY_ID } primaryKey="username" rangeKey="userid" data={{ age: GraphQLString, address: GraphQLString }} /> }; 

<Entry /> décrit la structure de nos données. Nous définissons nos noms pour primaryKey et rangeKey. Vous pouvez utiliser n'importe quel nom autre que certains mots clés DynamoDB que vous pouvez trouver ici. Mais les noms que nous utilisons sont les conséquences fonctionnelles:

  • Lorsque nous ajoutons des Ă©lĂ©ments Ă  notre base de donnĂ©es, nous devons fournir des valeurs pour ces noms de clĂ©s.
  • La combinaison des deux clĂ©s dĂ©crit un Ă©lĂ©ment unique dans la base de donnĂ©es.
  • Il ne doit pas y avoir d'autre <EntrĂ©e /> avec les mĂŞmes noms de clĂ© (un nom peut ĂŞtre le mĂŞme, mais pas les deux).
  • Nous ne pouvons trouver des Ă©lĂ©ments dans la base de donnĂ©es que lorsque nous avons la valeur d'au moins un nom de clĂ©.

Dans notre exemple, cela signifie que:

  • Chaque utilisateur doit avoir un nom d'utilisateur et userid.
  • il ne peut pas y avoir un deuxième utilisateur avec le mĂŞme nom d'utilisateur et le mĂŞme ID utilisateur. Du point de vue de la base de donnĂ©es, il serait bien que deux utilisateurs aient le mĂŞme nom d'utilisateur lorsqu'ils ont un ID utilisateur diffĂ©rent (ou vice versa).
  • Nous ne pouvons pas avoir une autre <EntrĂ©e /> dans notre base de donnĂ©es primaryKey = «nom d'utilisateur» et rangeKey = «userid».
  • Nous pouvons interroger la base de donnĂ©es pour les utilisateurs lorsque nous avons un nom d'utilisateur ou un id utilisateur. Mais nous ne pouvons pas demander par âge ou adresse.

Ajouter des éléments à la base de données


Nous avons défini deux composants <Service /> dans notre <ServiceOrientedApp /> . Service POST qui ajoute l'utilisateur à la base de données et service GET qui en récupère l'utilisateur.

Commençons par <AddUserService /> . Voici le code de ce service:

 import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ); import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ": "*", // requis pour CORS soutien au travail import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ;. import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; 

Le composant <Service /> - accepte trois paramètres:

  • L'identifiant ( id ) doit ĂŞtre une chaĂ®ne unique. Nous utilisons un identifiant ( id ), quand nous avons besoin de se rĂ©fĂ©rer au service de dans les autres composants.
  • Le path (avec initiale /) indique le chemin URL relatif de votre service
  • La method doit ĂŞtre l'une de GET , POST , UPDATE , DELETE . Il indique la requĂŞte HTTP que nous utilisons lors de l'appel du service.

Nous ajoutons <Middleware /> comme un élément enfant. Ce <Middleware /> prend une fonction de rappel comme paramètre. Nous pourrions fournir directement le middleware Express.js. Puisque nous voulons accéder à la base de données, nous serviceWithDataLayer fonction dans serviceWithDataLayer . Cela ajoute dataLayer comme premier paramètre à notre rappel.

DataLayer permet d'accéder à la base de données. Voyons comment!

Fonction Asynchrone mutate applique les modifications aux données contenues dans notre base de données. Cela nécessite le client et mutations commande en tant que paramètres.

Les données d'élément sont un objet Javascript qui possède toutes les paires clé-valeur nécessaires. Dans notre service, nous obtenons l'objet du corps de la demande. Pour l' User objet a la structure suivante:

 export interface IUserEntry { username: string, userid: string, age: string, address: string } d' export interface IUserEntry { username: string, userid: string, age: string, address: string } 

Cet objet prend les noms primaryKey et rangeKey et toutes les clés de données que nous avons définies dans <Entry /> .

Remarque: pour l'instant, le seul type pris en charge est une chaîne qui correspond à une chaîne GraphQLString dans la définition de <Entrée />.
Nous avons mentionné ci - dessus que nous prenons les données IUserEntry du corps. Comment ça se passe?

Les composants d'infrastructure fournissent la fonction asynchrone callService (serviceId, dataObject) . Cette fonction accepte un identifiant de service, un objet Javascript (pour l'envoi en tant que corps de requĂŞte lors de l'utilisation de POST ), une fonction de success et une fonction de rappel d'erreur.

Le code suivant montre comment nous utilisons cette fonction à notre <AddUserService /> . Nous serviceId . Et nous passons userData , que nous prenons comme paramètre à notre fonction.

 export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; données); export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; ) export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; 

Maintenant, la fonction callAddUserService est tout ce dont nous avons besoin lorsque nous voulons ajouter un nouvel utilisateur. Par exemple, appelez-le lorsque l'utilisateur clique sur un bouton:

 <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button> d' <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button> d' <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button> 

Nous appelons simplement à l'aide de l'objet IUserEntry . Il appelle le service correct (comme indiqué par son identifiant ( id )). Il place userData dans le corps de la requête. <AddUserService /> prend les données sur le corps et les met dans une base de données.

Récupérer des éléments de la base de données


Récupérer des éléments d'une base de données est aussi simple que de les ajouter.

 export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } dataLayer, req, res, à côté) { export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } ": "*", // requis pour CORS soutien au travail export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } 

Encore une fois, nous utilisons <service />, <Middleware /> et une fonction de rappel pour accéder à la base de données.

Au lieu de la fonction de mutate , ce qui ajoute un élément à la base de données, nous utilisons le select . Cette fonction demande au client, que nous prenons de dataLayer de dataLayer . La deuxième option - l'équipe de select . Comme la commande de mutation , nous pouvons créer une commande de select aide de dataLayer .

Cette fois -ci, nous utilisons le getEntryQuery . Nous fournissons un identifiant ( id ) <Entry /> , dont nous voulons obtenir l'article. Et nous fournissons les clés ( primaryKey et rangeKey ) d'un élément particulier dans un objet Javascript. Puisque nous fournissons les deux clés, nous récupérons un élément. Si elle existe.

Comme vous pouvez le voir, nous prenons les valeurs clés de la demande. Mais cette fois, nous les prenons de request.query , et non de request.body . La raison en est que ce service utilise la méthode GET . Cette méthode ne prend pas en charge le corps dans la demande. Mais il fournit toutes les données en tant que paramètres de requête.

Fonction callService gère pour nous. Comme dans la callAddUserService-function , nous fournissons l'identifiant ( id ) <Service /> que nous voulons appeler. Nous fournissons les données nécessaires. Il n'y a que les clés. Et nous fournissons des fonctions de rappel.

Un rappel réussie apporte une réponse. Le corps de la réponse au format json contient notre élément trouvé. Nous pouvons y accéder via l'élément clé get_user_entry . « Get_ » définit la demande que nous avons placée dans notre fonction de sélection. " User_entry " est la clé de notre <Entry /> .

 export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } ) export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } 

Jetez un œil à votre application Full-Stack en action.


Si vous n'avez pas encore démarré votre application, c'est le moment de le faire: npm run start-{your-env-name} .

Vous pouvez même déployer votre application sur AWS avec une seule commande: npm run deploy-{your-env-name} . (Ne pas oublier de mettre les informations d'identification dans le fichier des AWS).

Ce message ne décrit pas comment vous entrez les données que vous mettez dans la base de données et comment vous affichez les résultats. callAddUserService et callGetUserService encapsulent tout ce qui est spécifique aux services et bases de données. Vous venez de mettre l'objet Javascript à l'intérieur et de le récupérer.

Vous trouverez le code source de cet exemple dans ce référentiel GitHub . Il comprend une interface utilisateur très simple.

Source: https://habr.com/ru/post/fr476696/


All Articles