Fermer

juillet 26, 2018

Construire une application CRUD de base avec Node et React –


Cet article a été publié à l'origine le blog du développeur Okta . Merci de soutenir les partenaires qui rendent SitePoint possible.

Il y a beaucoup de frameworks JavaScript ici aujourd'hui. Il semble que j'entends parler d'un nouveau chaque mois ou deux. Ils ont tous leurs avantages et sont généralement là pour résoudre une sorte de problème avec un cadre existant. Mon préféré pour travailler avec jusqu'à présent a été React. L'une des meilleures choses à ce sujet est le nombre de composants et de bibliothèques open source dans l'écosystème React, vous avez donc beaucoup à choisir. Cela peut être vraiment difficile si vous êtes indécis, mais si vous aimez la liberté de faire les choses à votre façon alors React peut être la meilleure option pour vous.

Dans ce tutoriel, je vais vous guider à travers la création d'un web frontend application dans React et un serveur API REST backend dans Node. Le frontend aura une page d'accueil et un gestionnaire de posts, avec le gestionnaire de messages caché derrière l'authentification de l'utilisateur sécurisé. En tant que mesure de sécurité supplémentaire, le backend ne vous laissera pas non plus créer ou éditer des messages sauf si vous êtes correctement authentifié.

Le tutoriel utilisera OpenID Connect (OIDC) d'Okta pour gérer l'authentification. Sur le frontend, le Okta React SDK sera utilisé pour demander un jeton et le fournir dans les requêtes au serveur. Sur le backend, le Okta JWT Verifier s'assurera que l'utilisateur est correctement authentifié et enverra une erreur sinon

Le backend sera écrit avec Express comme serveur, avec Sequelize pour la modélisation et le stockage de données, et Epilogue pour créer rapidement une API REST sans beaucoup de texte standard.

Why React?

React a été l'un des plus populaires Bibliothèques JavaScript pour les dernières années. L'un des plus grands concepts, et ce qui le rend si rapide, est d'utiliser un DOM virtuel (le Document Object Model, ou DOM, est ce qui décrit la disposition d'une page web) et de faire de petites mises à jour au DOM réel. . React n'est pas la première bibliothèque à le faire, et il y en a plusieurs maintenant, mais cela a certainement rendu l'idée populaire. L'idée est que le DOM est lent, mais JavaScript est rapide, donc vous dites juste ce que vous voulez que la sortie finale ressemble et React fera ces changements au DOM dans les coulisses. Si aucun changement n'est nécessaire, cela n'affecte pas le DOM. Si seulement un petit champ de texte change, il va juste patcher cet élément.

React est aussi le plus souvent associé à JSX, même s'il est possible d'utiliser React sans JSX. JSX vous permet de mélanger HTML avec votre JavaScript. Plutôt que d'utiliser des modèles pour définir le code HTML et lier ces valeurs à un modèle de vue, vous pouvez tout écrire en JavaScript. Les valeurs peuvent être des objets JavaScript simples, au lieu de chaînes qui doivent être interprétées. Vous pouvez également écrire des composants React réutilisables qui finissent par ressembler à n'importe quel autre élément HTML dans votre code.

Voici un exemple de code JSX qui devrait être assez simple à suivre:

 const Form = () => (
  
); const App = () => (   

Bienvenue, Hitchhiker!

);

… et voici à quoi ressemblerait le même code si vous l'écriviez en JavaScript, sans utiliser JSX:

 const Form = () => React.createElement (
  "forme",
  nul,
  React.createElement (
    "étiquette",
    nul,
    "Prénom",
    React.createElement ("input", {value: "Arthur Dent"})
  ),
  React.createElement (
    "étiquette",
    nul,
    "Réponse à la vie, l'univers et tout ce qui existe",
    React.createElement ("input", {type: "nombre", valeur: 42})
  )
)

const App = () => React.createElement (
  "principale",
  nul,
  React.createElement (
    "h1",
    nul,
    "Bienvenue, auto-stoppeur!"
  ),
  React.createElement (Formulaire, null)
)

Je trouve la forme JSX beaucoup plus facile à lire, mais c'est comme, mon avis, mec.

Créer votre application React

Le moyen le plus rapide de démarrer avec React est d'utiliser Créer une application React un outil qui génère une application Web progressive (PWA) avec tous les scripts et toutes les fonctions cachées derrière quelque chose appelé reac-scripts vous pouvez donc vous concentrer sur l'écriture de code. Il a aussi toutes sortes de fonctionnalités de développement intéressantes, comme la mise à jour du code à chaque fois que vous apportez des modifications, et des scripts pour le compiler en production. Vous pouvez utiliser npm ou yarn mais je vais utiliser yarn dans ce tutoriel.

Pour installer create-react-app et fil exécutez simplement:

 npm i -g create-react-app@1.5.2 yarn@1.7.0

NOTE : Je vais ajouter des numéros de version pour aider à assurer la pérennité de ce post. En général, vous ne devriez pas utiliser les numéros de version (par exemple, npm i -g create-react-app ).

Maintenant, amorcez votre application avec les commandes suivantes:

 create- Réagir-app my-react-app
cd my-react-app
début de fil

L'application par défaut devrait maintenant tourner sur le port 3000. Jetez un coup d'œil sur http: // localhost: 3000 .

 Créer une page d'accueil par défaut de l'application React

dans Réagir avec l'interface utilisateur matérielle

Pour garder les choses belles sans écrire beaucoup de CSS supplémentaire, vous pouvez utiliser un cadre d'interface utilisateur. Material UI est un excellent framework pour React qui implémente les principes de Google Material Design

Ajouter la dépendance avec:

 yarn add @ material-ui / core @ 1.3.1

Le matériau recommande la police Roboto. Vous pouvez l'ajouter à votre projet en éditant public / index.html et en ajoutant la ligne suivante dans la balise head :

  

Vous pouvez séparer les composants en fichiers séparés pour aider à organiser les choses. Tout d'abord, créez quelques nouveaux dossiers dans votre répertoire src : composants et pages

 mkdir src / components
mkdir src / pages

Créez maintenant un composant AppHeader . Cela servira comme la barre de navigation avec des liens vers des pages, ainsi que montrer le titre et si vous êtes connecté.

src / components / AppHeader.js

 importer React à partir de 'react' ;
import {
  AppBar,
  Barre d'outils,
  Typographie,
} à partir de '@ material-ui / core';

const AppHeader = () => (
  
    
      
         Mon application Réagir
      
    
  
);

exporter par défaut AppHeader;

Créer également une page d'accueil:

src / pages / Home.js

 importer React à partir de 'react';
import {
  Typographie,
} à partir de '@ material-ui / core';

export default () => (
   Bienvenue à la maison! 
)

Maintenant allez-y et en fait juste gut l'application exemple, en remplaçant src / App.js par ce qui suit:

src / App.js

 import React, {Fragment} de 'réagir';
import {
  CssBaseline,
  avecStyles,
} à partir de '@ material-ui / core';

importer AppHeader à partir de './components/AppHeader';
import Home depuis './pages/Home';

const styles = theme => ({
  principale: {
    rembourrage: 3 * theme.spacing.unit,
    [theme.breakpoints.down('xs')]: {
      remplissage: 2 * theme.spacing.unit,
    },
  },
});

const App = ({classes}) => (
  
    
    
    
); exporter par défaut avecStyles (styles) (App);

L'interface utilisateur matérielle utilise JSS (un des nombreux styles dans la tendance de plus en plus populaire de CSS en JavaScript), ce qui est fourni par withStyles

Le composant CssBaseline ajoutera CSS par défaut à la page (par exemple enlever les marges du corps), donc nous n'avons plus besoin de src / index.css . Vous pouvez également vous débarrasser de quelques autres fichiers, maintenant que nous avons supprimé la plupart des applications Hello World .

 rm src / index.css src / App.css src / logo .svg

Dans src / index.js supprimez la référence à index.css (la ligne indiquant import './index.css';) . Pendant que vous y êtes, ajoutez ce qui suit comme toute dernière ligne de src / index.js pour activer le rechargement de module chaud, ce qui rendra les modifications que vous effectuez automatiquement dans l'application sans avoir besoin pour actualiser la page entière:

 if (module.hot) module.hot.accept ();

À ce stade, votre application devrait ressembler à ceci:

 Page d'accueil vide

Ajouter l'authentification à votre nœud + Réagir avec Okta

Vous ne livreriez jamais votre nouvelle application sur Internet sans sécurisé gestion de l'identité non? Eh bien, Okta rend cela beaucoup plus facile et plus évolutif que ce à quoi vous êtes probablement habitué. Okta est un service de cloud qui permet aux développeurs de créer, de modifier et de stocker en toute sécurité des comptes d'utilisateurs et des données de compte d'utilisateur, et de les connecter à une ou plusieurs applications. Notre API vous permet de:

Si vous n'en possédez pas encore, abonnez-vous à un compte de développeur pour toujours . Connectez-vous à votre console de développeur, accédez à Applications puis cliquez sur Ajouter une application . Sélectionnez Single-Page App puis Suivant .

Puisque Create React App s'exécute sur le port 3000 par défaut, vous devez l'ajouter comme URI de base et URI de redirection de connexion. Vos paramètres doivent ressembler à ceci:

 Créer de nouveaux paramètres d'application

Cliquez sur Terminé pour enregistrer votre application, puis copiez votre ID client et collez comme une variable dans un fichier appelé .env.local à la racine de votre projet. Cela vous permettra d'accéder au fichier dans votre code sans avoir besoin de stocker les informations d'identification dans le contrôle de la source. Vous devrez également ajouter l'URL de votre organisation (sans le suffixe -admin ). Les variables d'environnement (autres que NODE_ENV ) doivent commencer par REACT_APP_ pour que l'application Create React les lise, ainsi le fichier devrait ressembler à ceci:

.env.local

 REACT_APP_OKTA_CLIENT_ID = {votreClientId}
REACT_APP_OKTA_ORG_URL = https: // {yourOktaDomain}

Le moyen le plus simple d'ajouter l'authentification avec Okta à une application React est d'utiliser Okta's React SDK . Vous devrez également ajouter des routes, ce qui peut être fait en utilisant React Router . Je vais aussi commencer à ajouter des icônes à l'application (pour l'instant comme une icône d'avatar pour montrer que vous êtes connecté). L'interface utilisateur matérielle fournit des icônes de matériau, mais dans un autre package, vous devrez donc également l'ajouter. Exécutez la commande suivante pour ajouter ces nouvelles dépendances:

 yarn add @ okta / okta-réagit @ 1.0.2 react-router-dom@4.3.1 @ material-ui / icons @ 1.1.0

Pour que les routes fonctionnent correctement dans React, vous devez envelopper votre application entière dans un routeur . De même, pour autoriser l'accès à l'authentification n'importe où dans l'application, vous devez envelopper l'application dans un composant Security fourni par Okta. Okta doit également avoir accès au routeur. Le composant Security doit donc être imbriqué dans le routeur. Vous devez modifier votre fichier src / index.js pour ressembler à ceci:

src / index.js

 import Réagir à partir de 'react';
importer ReactDOM à partir de 'react-dom';
importer {BrowserRouter} à partir de 'react-router-dom';
importer {Sécurité} à partir de '@ okta / okta-react';

importer une application à partir de './App';
importer registerServiceWorker à partir de './registerServiceWorker';

const oktaConfig = {
  émetteur: `$ {process.env.REACT_APP_OKTA_ORG_URL} / oauth2 / default`,
  redirect_uri: `$ {window.location.origin} / implicite / callback`,
  client_id: process.env.REACT_APP_OKTA_CLIENT_ID,
}

ReactDOM.render (
  
    
      
    
  ,
  document.getElementById ('root'),
)
registerServiceWorker ();

if (module.hot) module.hot.accept ();

Maintenant dans src / App.js vous pouvez utiliser Route s. Ceux-ci indiquent à l'application de ne rendre qu'un certain composant si l'URL actuelle correspond au chemin donné . Remplacez votre composant Home par une route qui ne restitue le composant que lorsque vous pointez sur l'URL racine ( / ) et affiche le composant ImplicitCallback d'Okta pour le / implicite / callback chemin d'accès

src / App.js

 --- a / src / App.js
+++ b / src / App.js
@@ -1,4 +1,6 @@
 importer React, {Fragment} de 'reagir';
+ import {Route} de 'react-router-dom';
+ importer {ImplicitCallback} de '@ okta / okta-react';
 import {
   CssBaseline,
   avecStyles,
@@ -21,7 +23,8 @@ const App = ({classes}) => (
     
     
     
- + +
);

Ensuite, vous avez besoin d'un bouton de connexion. Ce fichier est un peu plus volumineux car il contient une certaine logique pour vérifier si l'utilisateur est authentifié. Je vais d'abord vous montrer tout le composant, puis parcourir ce que chaque section est en train de faire:

src / components / LoginButton.js

 import React, {Component} de 'react';
import {
  Bouton,
  IconButton,
  Menu,
  Élément du menu,
  ListItemText,
} à partir de '@ material-ui / core';
importer {AccountCircle} à partir de '@ material-ui / icons';
importer {withAuth} à partir de '@ okta / okta-react';

class LoginButton étend le composant {
  état = {
    authentifié: null,
    utilisateur: null,
    menuAnchorEl: null,
  }

  componentDidUpdate () {
    this.checkAuthentication ();
  }

  componentDidMount () {
    this.checkAuthentication ();
  }

  async checkAuthentication () {
    const authentifié = attendre this.props.auth.isAuthenticated ();
    if (authentifié! == this.state.authenticated) {
      const utilisateur = wait this.props.auth.getUser ();
      this.setState ({authentifié, utilisateur});
    }
  }

  login = () => this.props.auth.login ();
  déconnexion = () => {
    this.handleMenuClose ();
    this.props.auth.logout ();
  }

  handleMenuOpen = événement => this.setState ({menuAnchorEl: event.currentTarget});
  handleMenuClose = () => this.setState ({menuAnchorEl: null});

  render () {
    const {authentifié, utilisateur, menuAnchorEl} = this.state;

    if (authentifié == null) renvoie null;
    if (! authenticated) return ;

    const menuPosition = {
      vertical: 'top',
      horizontal: 'juste',
    }

    revenir (
      
);   } } exporter par défaut avecAuth (LoginButton);

Les composants de réaction ont un concept de gestion d'état. Chaque composant peut être passé accessoires (dans un composant comme type et numéro serait considéré comme des accessoires). Ils peuvent également maintenir leur propre état, qui a quelques valeurs initiales et peut être changé avec une fonction appelée setState . Chaque fois que les accessoires ou changent le composant se rediffuse, et si des changements doivent être apportés au DOM, ils se produiront alors. Dans un composant, vous pouvez y accéder avec this.props ou this.state respectivement.

Ici, vous créez un nouveau composant React et définissez les valeurs d'état initiales . Tant que vous n'avez pas interrogé le auth prop, vous ne savez pas s'il y a un utilisateur ou pas, donc vous réglez authentifié et utilisateur à null ] L'interface utilisateur matérielle utilisera menuAnchorEl pour savoir où ancrer le menu qui vous permet de déconnecter l'utilisateur.

 class LoginButton extends Component {
  état = {
    authentifié: null,
    utilisateur: null,
    menuAnchorEl: null,
  }

  // ...
}

Les composants React ont également leurs propres méthodes de cycle de vie, qui sont des hooks que vous pouvez utiliser pour déclencher des actions à certaines étapes du cycle de vie du composant. Ici, lorsque le composant est monté pour la première fois, vous vérifiez si l'utilisateur a été authentifié ou non et, si c'est le cas, obtenez plus de détails sur l'utilisateur, tels que son nom et son adresse e-mail. Vous devez également relancer cette vérification à chaque mise à jour du composant, mais vous devez faire attention à ne mettre à jour l'état que lorsque quelque chose est différent, sinon vous vous retrouverez dans une boucle infinie (le composant se met à jour) , qui met à jour le composant, vous lui donnez de nouvelles valeurs, etc.). La fonction withAuth est un composant d'ordre supérieur (HOC) qui enveloppe le composant d'origine et renvoie un autre contenant le prop prop.

 class LoginButton extends Component {
  // ...

  componentDidUpdate () {
    this.checkAuthentication ();
  }

  componentDidMount () {
    this.checkAuthentication ();
  }

  async checkAuthentication () {
    const authentifié = attendre this.props.auth.isAuthenticated ();
    if (authentifié! == this.state.authenticated) {
      const utilisateur = wait this.props.auth.getUser ();
      this.setState ({authentifié, utilisateur});
    }
  }

  // ...
}

exporter par défaut avecAuth (LoginButton);

Les fonctions suivantes sont des fonctions auxiliaires utilisées plus tard pour consigner ou déconnecter l'utilisateur, et ouvrir ou fermer le menu. L'écriture de la fonction comme une fonction de flèche assure que cela se réfère à l'instanciation du composant. Sans cela, si une fonction est appelée quelque part en dehors du composant (par exemple dans un événement onClick ), vous perdriez l'accès au composant et ne seriez pas capable d'exécuter des fonctions ou d'accéder les accessoires ou état .

 class LoginButton extends Component {
  // ...

  login = () => this.props.auth.login ();
  déconnexion = () => {
    this.handleMenuClose ();
    this.props.auth.logout ();
  }

  handleMenuOpen = événement => this.setState ({menuAnchorEl: event.currentTarget});
}

Tous les composants React doivent avoir une fonction render () . C'est ce qui indique à React ce qu'il faut afficher à l'écran, même s'il ne devrait rien afficher (auquel cas vous pouvez retourner null ).

Si vous n'êtes pas encore sûr de l'état d'authentification , vous pouvez simplement retourner null pour que le bouton ne soit pas rendu du tout. Une fois Okta retourne this.props.auth.isAuthenticated () la valeur sera true ou false . Si c'est false vous devrez fournir un bouton Login . Si l'utilisateur est connecté, vous pouvez afficher une icône d'avatar avec un bouton de déconnexion.

 class LoginButton extends Component {
  // ...

  render () {
    const {authentifié, utilisateur, menuAnchorEl} = this.state;

    if (authentifié == null) renvoie null;
    if (! authenticated) return ;

    const menuPosition = {
      vertical: 'top',
      horizontal: 'juste',
    }

    revenir (
      
);   } }

La prochaine pièce du puzzle est d'ajouter ce composant LoginButton à votre en-tête. Afin de l'afficher sur le côté droit de la page, vous pouvez mettre une entretoise vide div qui a une valeur de [1.759030] flex de 1. Comme les autres objets ne sont pas dit Pour fléchir, l'entretoise occupera autant d'espace que possible. Modifiez votre fichier src / components / AppHeader.js comme suit:

src / components / AppHeader.js

 --- a / src / components / AppHeader.js
+++ b / src / components / AppHeader.js
@@ -3,16 +3,27 @@ import {
   AppBar,
   Barre d'outils,
   Typographie,
+ avecStyles,
 } à partir de '@ material-ui / core';

-const AppHeader = () => (
+ importer LoginButton à partir de './LoginButton';
+
+ const styles = {
+ flex: {
+ flex: 1,
+},
+};
+
+ const AppHeader = ({classes}) => (
   
     
       
          L'application My React
       
+ 
+ ); -exporter par défaut AppHeader; + export par défaut avec styles (styles) (AppHeader);

Vous devriez maintenant pouvoir vous connecter et vous déconnecter de votre application en utilisant le bouton en haut à droite

 page d'accueil avec le bouton de connexion

Lorsque vous cliquez sur le bouton Connexion, vous aurez être redirigé vers votre URL d'organisation Okta pour gérer l'authentification. Vous pouvez vous connecter avec les mêmes informations d'identification que vous utilisez dans votre console de développeur.

 Okta se connecter

Une fois connecté, vous êtes de nouveau redirigé vers votre application. que vous êtes connecté. Si vous cliquez sur l'icône, votre nom s'affiche dans un bouton de déconnexion. Un clic sur le bouton vous permet de rester sur la page d'accueil mais de vous déconnecter à nouveau.

 homepage, connecté

 page d'accueil sans bouton de déconnexion

Ajouter un nœud Serveur API REST

que les utilisateurs peuvent s'authentifier en toute sécurité, vous pouvez créer le serveur API REST pour effectuer des opérations CRUD sur un modèle de publication. Vous devrez ajouter quelques dépendances à votre projet à ce stade:

 yarn add @ okta / jwt-verifier @ 0.0.12 body-parser@1.18.3 cors@2.8.4 dotenv@6.0.0 epilogue @ 0.7.1 express @ 4.16.3 sequelize@4.38.0 sqlite@2.9.2
fil ajouter -D npm-run-all@4.1.3

Créez un nouveau dossier pour le serveur sous le répertoire src:

 mkdir src / server

Créez maintenant un nouveau fichier src / server / index.js . Pour garder cela simple, nous allons simplement utiliser un seul fichier, mais vous pourriez avoir un sous-arbre entier de fichiers dans ce dossier. Le conserver dans un dossier séparé vous permet de surveiller uniquement les modifications dans ce sous-répertoire et de recharger le serveur uniquement lorsque vous apportez des modifications à ce fichier, au lieu de tout fichier modifié dans src . Encore une fois, je vais poster le fichier entier et ensuite expliquer quelques sections clés ci-dessous.

src / server / index.js

 require ('dotenv'). Config ({chemin: '. env.local '});

const express = require ('express');
const cors = require ('cors');
const bodyParser = require ('body-parser');
const Sequelize = require ('sequelize');
const epilogue = require ('épilogue');
const OktaJwtVerifier = require ('@ okta / jwt-verifier');

const oktaJwtVerifier = nouveau OktaJwtVerifier ({
  clientId: process.env.REACT_APP_OKTA_CLIENT_ID,
  émetteur: `$ {process.env.REACT_APP_OKTA_ORG_URL} / oauth2 / default`,
});

const app = express ();
app.use (cors ());
app.use (bodyParser.json ());

app.use (async (req, res, next) => {
  essayez {
    if (! req.headers.authorization) lance une nouvelle erreur ('L'en-tête d'autorisation est obligatoire');

    const accessToken = req.headers.authorization.trim (). split ('') [1];
    attendez oktaJwtVerifier.verifyAccessToken (accessToken);
    prochain();
  } catch (erreur) {
    suivant (error.message);
  }
});

base de données const = new Sequelize ({
  dialecte: 'sqlite',
  stockage: './test.sqlite',
});

const Post = database.define ('posts', {
  titre: Sequelize.STRING,
  corps: Sequelize.TEXT,
});

epilogue.initialize ({app, sequelize: base de données});

epilogue.resource ({
  modèle: Post,
  paramètres: ['/posts', '/posts/:id'],
});

port const = process.env.SERVER_PORT || 3001;

database.sync (). then (() => {
  app.listen (port, () => {
    console.log (`Listening sur le port $ {port}`);
  });
});

Ce qui suit charge les variables d'environnement que nous avons utilisées dans l'application React. De cette façon, nous pouvons utiliser les mêmes variables d'environnement, et seulement les placer à un endroit.

 require ('dotenv'). Config ({path: '.env.local'});

Ceci configure le serveur HTTP et ajoute quelques paramètres pour permettre le partage de ressources d'origine (CORS) et analysera automatiquement JSON.

 const app = express ();
app.use (cors ());
app.use (bodyParser.json ());

Voici où vous vérifiez qu'un utilisateur est correctement authentifié. D'abord, lancez une erreur s'il n'y a pas d'en-tête Authorization ce qui vous permettra d'envoyer le jeton d'autorisation. Le jeton ressemblera à Bearer aLongBase64String . Vous souhaitez transmettre la chaîne Base 64 au vérificateur Okta JWT pour vérifier que l'utilisateur est correctement authentifié. Le vérificateur envoie initialement une demande à l'émetteur pour obtenir une liste de signatures valides, puis vérifie localement que le jeton est valide. Sur les demandes suivantes, ceci peut être fait localement à moins qu'il ne trouve une affirmation selon laquelle il n'a pas encore de signatures.

Si tout semble bon, l'appel à next () dit à Express d'aller de l'avant et continuer à traiter la demande. Si toutefois, la demande est invalide, une erreur sera lancée. L'erreur est ensuite passée dans suivant pour dire à Express que quelque chose s'est mal passé. Express va alors renvoyer une erreur au client au lieu de continuer.

 app.use (async (req, res, next) => {
  essayez {
    if (! req.headers.authorization) lance une nouvelle erreur ('L'en-tête d'autorisation est obligatoire');

    const accessToken = req.headers.authorization.trim (). split ('') [1];
    attendez oktaJwtVerifier.verifyAccessToken (accessToken);
    prochain();
  } catch (erreur) {
    suivant (error.message);
  }
});

Voici où vous configurez Sequelize. C'est un moyen rapide de créer des modèles de base de données. Vous pouvez utiliser Sequelize avec une grande variété de bases de données, mais ici vous pouvez simplement utiliser SQLite pour démarrer rapidement sans aucune autre dépendance.

 const database = new Sequelize ({
  dialecte: 'sqlite',
  stockage: './test.sqlite',
});

const Post = database.define ('posts', {
  titre: Sequelize.STRING,
  corps: Sequelize.TEXT,
});

Epilogue fonctionne bien avec Sequelize et Express. Il lie les deux ensemble comme de la colle, créant un ensemble de points de terminaison CRUD avec juste quelques lignes de code. Tout d'abord, vous initialisez Epilogue avec l'application Express et le modèle de base de données Sequelize. Ensuite, vous lui dites de créer vos points de terminaison pour le modèle Post : un pour une liste de postes, qui auront des méthodes POST et GET ; et un pour les publications individuelles, qui auront les méthodes GET PUT et SUPPRIMER .

 epilogue.initialize ({app, sequelize: database}) ;

epilogue.resource ({
  modèle: Post,
  paramètres: ['/posts', '/posts/:id'],
});

La dernière partie du serveur est l'endroit où vous dites à Express de commencer à écouter les requêtes HTTP. Vous devez dire à sequelize d'initialiser la base de données, et quand c'est fait c'est OK pour Express de commencer à écouter sur le port que vous décidez. Par défaut, puisque l'application React utilise 3000 nous en ajouterons un pour le rendre 3001 .

 const port = process.env.SERVER_PORT || 3001;

database.sync (). then (() => {
  app.listen (port, () => {
    console.log (`Listening sur le port $ {port}`);
  });
});

Vous pouvez maintenant apporter quelques petites modifications à package.json pour faciliter l'exécution de l'interface et du backend en même temps. Remplacez le script par défaut start et ajoutez deux autres, de sorte que votre section de scripts ressemble à ceci:

package.json

 "scripts": {
    "start": "npm-run-all --parallel watch: démarrage du serveur: web",
    "start: web": "reac-scripts start",
    "start: server": "nœud src / server",
    "watch: server": "nodemon --watch src / serveur src / serveur",
    "build": "construire des scripts de réaction",
    "test": "test de scripts de réaction --env = jsdom",
    "éjecter": "react-scripts eject"
  }

Maintenant, vous pouvez simplement lancer Yarn Start et le serveur et l'application React fonctionneront en même temps, rechargeant chaque fois que des modifications pertinentes sont apportées. Si vous devez modifier le port pour une raison quelconque, vous pouvez modifier le port de l'application React et le port du serveur avec les variables d'environnement PORT et SERVER_PORT respectivement. Par exemple, PORT = 8080 SERVER_PORT = 8081 début de fil .

Ajouter la page Posts Manager à votre Noeud + Réagir App

Maintenant que vous avez un backend Node pour gérer vos messages, vous pouvez lier jusqu'à l'interface React en ajoutant une autre page. Cela enverra les demandes d'extraction, de création, de modification et de suppression des publications. Il enverra également le jeton d'autorisation requis avec chaque requête afin que le serveur sache que vous êtes un utilisateur valide.

Une bonne chose à propos de React Router est qu'il vous permet d'utiliser des variables dans l'URL. Cela nous permettra d'utiliser l'identifiant d'un message dans l'URL, donc vous pouvez aller à / posts / 2 pour voir le numéro de poste 2. Dans cet esprit, vous pouvez créer un modal qui sera ouvert chaque fois que vous êtes sur cette partie de la page, et pour fermer le modal tout ce que vous auriez à faire est de revenir à / posts .

Forms in React peut être un peu pénible. Vous pouvez utiliser un élément de base mais vous devrez également écouter les événements onChange mettre à jour l'état du composant et définir la nouvelle valeur sur l'entrée éléments. Pour rendre les formulaires plus faciles, il existe au moins quelques bibliothèques, mais je vais vous montrer comment utiliser React Final Form pour découper une grande partie de la liste déroulante.

Vous aurez également besoin recomposent lodash et moment pour certaines fonctions d'aide. Vous pouvez les installer tous en tant que dépendances avec la commande suivante:

 yarn add react-final-form@3.6.4 final-form@4.8.3 recompose@0.27.1 lodash@4.17.10 moment@2.22.2

Créer un composant éditeur de publication

Créer un composant PostEditor qui sera utilisé dans la page Gestionnaire de publication. Pour l'instant, les articles auront juste le titre et le corps les champs.

src / components / PostEditor.js

 import React de 'react' ;
import {
  avecStyles,
  Carte,
  CardContent,
  CardActions,
  Modal,
  Bouton,
  Champ de texte,
} à partir de '@ material-ui / core';
import {compose} à partir de 'recompose';
importer {withRouter} à partir de 'react-router-dom';
importer {Form, Field} à partir de 'react-final-form';

const styles = theme => ({
  modal: {
    affichage: 'flex',
    alignItems: 'centre',
    justifyContent: 'centre',
  },
  modalCard: {
    largeur: '90% ',
    maxWidth: 500,
  },
  modalCardContent: {
    affichage: 'flex',
    flexDirection: 'colonne',
  },
  marginTop: {
    marginTop: 2 * theme.spacing.unit,
  },
});

const PostEditor = ({classes, post, onSave, history}) => (
  
    {({handleSubmit}) => (
       history.goBack ()}
        ouvrir
      >
        
          
            
              
                 {({input}) => }
              
              
                 {({input}) => (
                  
                )}
              
            
            
              
              
            
          
        
      
    )}
  
)

exporter par défaut composer (
  avecRouter,
  avecStyles (styles),
) (PostEditor);

Création du composant Page Posts Manager

Vous aurez également besoin d'une page pour afficher une liste de messages et pour injecter l'éditeur de publication. Créez un nouveau fichier src / pages / PostsManager.js . Une fois de plus, je posterai le fichier entier puis je vous guiderai dans chaque section.

src / pages / PostsManager.js

 import Réagit, {Composant, Fragment} de 'réagissent';
importer {withAuth} à partir de '@ okta / okta-react';
importer {withRouter, Route, Redirect, Link} à partir de 'react-router-dom';
import {
  avecStyles,
  Typographie,
  Bouton,
  IconButton,
  Papier,
  Liste,
  ListItem,
  ListItemText,
  ListItemSecondaryAction,
} à partir de '@ material-ui / core';
importer {Delete as DeleteIcon, Add as AddIcon} à partir de '@ material-ui / icons';
importez le moment à partir de 'moment';
import {find, orderBy} à partir de 'lodash';
import {compose} à partir de 'recompose';

importer PostEditor à partir de '../components/PostEditor';

const styles = theme => ({
  des postes: {
    marginTop: 2 * theme.spacing.unit,
  },
  fab: {
    position: "absolue",
    en bas: 3 * theme.spacing.unit,
    à droite: 3 * theme.spacing.unit,
    [theme.breakpoints.down('xs')]: {
      en bas: 2 * theme.spacing.unit,
      à droite: 2 * theme.spacing.unit,
    },
  },
});

const API = process.env.REACT_APP_API || 'http: // localhost: 3001';

classe PostsManager étend le composant {
  état = {
    chargement: vrai,
    messages: [],
  }

  componentDidMount () {
    this.getPosts ();
  }

  async fetch (méthode, endpoint, body) {
    essayez {
      const response = wait fetch (`$ {API} $ {endpoint}`, {
        méthode,
        corps: corps && JSON.stringify (corps),
        en-têtes: {
          'type de contenu': 'application / json',
          accept: 'application/json',
          authorization: `Bearer ${await this.props.auth.getAccessToken()}`,
        },
      });
      return await response.json();
    } catch (error) {
      console.error(error);
    }
  }

  async getPosts() {
    this.setState({ loading: false, posts: await this.fetch('get', '/posts') });
  }

  savePost = async (post) => {
    if (post.id) {
      await this.fetch('put', `/posts/${post.id}`, post);
    } else {
      await this.fetch('post', '/posts', post);
    }

    this.props.history.goBack();
    this.getPosts();
  }

  async deletePost(post) {
    if (window.confirm(`Are you sure you want to delete "${post.title}"`)) {
      await this.fetch('delete', `/posts/${post.id}`);
      this.getPosts();
    }
  }

  renderPostEditor = ({ match: { params: { id } } }) => {
    if (this.state.loading) return null;
    const post = find(this.state.posts, { id: Number(id) });

    if (!post && id !== 'new') return ;

    return ;
  }

  render() {
    const { classes } = this.props;

    return (
      
        Posts Manager
        {this.state.posts.length > 0 ? (
          
            
              {orderBy(this.state.posts, ['updatedAt', 'title']['desc', 'asc']).map(post => (
                
                  
                  
                     this.deletePost(post)} color="inherit">
                      
                    
                  
                
              ))}
            
          
        ) : (
          !this.state.loading && No posts to display
        )}
        
        
      
    );
  }
}

export default compose(
  withAuth,
  withRouter,
  withStyles(styles),
)(PostsManager);

The backend is set to run on port 3001 on your local machine by default, so this sets that as a fallback. However, if you’d like to run this on another server, or on another port, you’ll need a way to edit that. You could run the app with API=https://api.example.com yarn start:web to override this.

const API = process.env.REACT_APP_API || 'http://localhost:3001';

When the component first mounts, you won’t have any data yet. You may want some indicator that the page is still loading, so setting the state to loading: true lets you know that later on. Setting the initial posts to an empty array makes the code simpler later since you can just always assume you have an array, even if it’s empty. Then you’ll want to fetch the set of posts as soon as the component mounts.

class PostsManager extends Component {
  state = {
    loading: true,
    posts: [],
  }

  componentDidMount () {
    this.getPosts();
  }

  // ...
}

Here you’re setting up a simple helper function to send a request to the server. This uses the fetch function that’s built into all modern browsers. The helper accepts a method (e.g. getpostdelete), an endpoint (here it would either be /posts or a specific post like /posts/3), and a body (some optional JSON value, in this case the post content).

This also sets some headers to tell the backend that any body it sends will be in JSON format, and it sets the authorization header by fetching the access token from Okta.

class PostsManager extends Component {
  // ...

  async fetch(method, endpoint, body) {
    try {
      const response = await fetch(`${API}${endpoint}`, {
        method,
        body: body && JSON.stringify(body),
        headers: {
          'content-type': 'application/json',
          accept: 'application/json',
          authorization: `Bearer ${await this.props.auth.getAccessToken()}`,
        },
      });
      return await response.json();
    } catch (error) {
      console.error(error);
    }
  }

  // ...
}

Here is where you call the fetch helper function.

You have one function to fetch posts (getPosts), which will also set loading to false since it’s the function that gets called when the component first loads.

There’s another function to save posts, which handles the case of adding a new post as well as modifying an existing post. Since the posts will be loaded in a modal based on the route, once the post is updated the browser is told to go back to /posts.

The last function is to delete a post. The confirm function actually blocks the UI, so it’s not normally recommended for an app like this, but it works well for demo purposes. It’s a built-in browser function that simply gives a popup asking you to confirm, and returns either true or false depending on your answer.

After saving or deleting a post, the getPosts command is called again to make sure that all the posts are up to date.

class PostsManager extends Component {
  // ...

  async getPosts() {
    this.setState({ loading: false, posts: await this.fetch('get', '/posts') });
  }

  savePost = async (post) => {
    if (post.id) {
      await this.fetch('put', `/posts/${post.id}`, post);
    } else {
      await this.fetch('post', '/posts', post);
    }

    this.props.history.goBack();
    this.getPosts();
  }

  async deletePost(post) {
    if (window.confirm(`Are you sure you want to delete "${post.title}"`)) {
      await this.fetch('delete', `/posts/${post.id}`);
      this.getPosts();
    }
  }

  // ...
}

The renderPostEditor function will be passed into a Route so that it only renders when you’re looking at a specific post. If you’re still loading posts, you won’t want to render anything just yet, so you can just return null. After the posts are loaded, you can use the id param to look for a post that matches. If you don’t find one for some reason, you should redirect back to the /posts page, since it’s likely a bad URL (maybe that post was already deleted).

The only exception is for a special route /posts/newwhich will be used to create a new post. In that case, you don’t want to redirect. Now that you have a post model, you can render the PostEditor component from above and pass the model to it to render in a modal.

class PostsManager extends Component {
  // ...

  renderPostEditor = ({ match: { params: { id } } }) => {
    if (this.state.loading) return null;
    const post = find(this.state.posts, { id: Number(id) });

    if (!post && id !== 'new') return ;

    return ;
  }

  // ...
}

Here is the main render function. When there are no posts, it should display a message “No posts to display”, except when the posts are still loading. You could choose to render a loading symbol, but for now just rendering nothing will suffice.

When there are posts, it renders a simple list of them, with the main text being the title of the post, and some subtext saying when it was last updated. The updated text uses moment for rendering a user-friendly string like 10 minutes ago instead of the raw timestamp.

By adding component={Link} and the to value, you are actually turning the list item into a link that takes you to the path of the post (e.g. /posts/5). You can do the same to send you to create a new post, by creating the Floating Action Button (FAB) that you see on many Material Design apps.

class PostsManager extends Component {
  // ...

  render() {
    const { classes } = this.props;

    return (
      
        Posts Manager
        {this.state.posts.length > 0 ? (
          
            
              {orderBy(this.state.posts, ['updatedAt', 'title']['desc', 'asc']).map(post => (
                
                  
                  
                     this.deletePost(post)} color="inherit">
                      
                    
                  
                
              ))}
            
          
        ) : (
          !this.state.loading && No posts to display
        )}
        
        
      
    );
  }
}

In order to get access to the Okta SDK, you need to use the withAuth HOC again. This time there are actually a few other HOCs to add, so you can use a utility function called compose from to wrap your component with multiple HOCs.

export default compose(
  withAuth,
  withRouter,
  withStyles(styles),
)(PostsManager);

OK, you’re in the home stretch now. You just need to tell the app when to render the Posts Manager page, and a link to get there.

Add the PostsManager page to src/App.js. Okta provides a SecureRoute component which is an extension of React Router’s Route component. This will ensure that if you try to go to that page and aren’t logged in, you’ll be redirected to sign in. If you’re on that page and you sign out, you’ll be redirected home.

src/App.js

--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,6 @@
 import React, { Fragment } from 'react';
 import { Route } from 'react-router-dom';
-import { ImplicitCallback } from '@okta/okta-react';
+import { SecureRoute, ImplicitCallback } from '@okta/okta-react';
 import {
   CssBaseline,
   withStyles,
@@ -8,6 +8,7 @@ import {

 import AppHeader from './components/AppHeader';
 import Home from './pages/Home';
+import PostsManager from './pages/PostsManager';

 const styles = theme => ({
   main: {
@@ -24,6 +25,7 @@ const App = ({ classes }) => (
     
     
+              
   

You also need to add a couple links to get to the Posts Manager and back to the Home page. You can do this in the App Header component you made earlier.

src/components/AppHeader.js

--- a/src/components/AppHeader.js
+++ b/src/components/AppHeader.js
@@ -1,6 +1,8 @@
 import React from 'react';
+import { Link } from 'react-router-dom';
 import {
   AppBar,
+  Button,
   Toolbar,
   Typography,
   withStyles,
@@ -20,6 +22,8 @@ const AppHeader = ({ classes }) => (
       
         My React App
       
+      
+      
       

Test Your React + Node CRUD App

You now have a fully functioning Single Page App, connected to a REST API server, secured with authentication via Okta’s OIDC.

Go ahead and test out the app now. If they’re not already running, make sure to start the server and the frontend. In your terminal run yarn start from your project directory.

Navigate to http://localhost:3000. You should be able to add, edit, view, and delete posts to your heart’s desire!

new post

list of posts

Learn More About React, Node, and Okta

Hopefully you found this article helpful. If you’re new to React, maybe you’re one step closer to deciding whether you love it or hate it. If you’re a React veteran, maybe you found out how easy it can be to add authentication to a new or existing app. Or maybe you learned a bit about Node.

If you’d like to view the source code for the example application in this post, you can find it at https://github.com/oktadeveloper/okta-react-node-example.

If you’re still aching for more content, there is a plethora of great posts on the Okta developer blog. This post was not-so-loosely based on Build a Basic CRUD App with Vue.js and Nodewhich I would definitely recommend checking out if you’re interested in learning more about Vue.js. Here are some other great articles to check out as well:

And as always, we’d love to hear from you. Hit us up with questions or feedback in the comments, or on Twitter @oktadev.

Build a Basic CRUD App with Node and React‘ was originally published on the Okta developer blog on July 10, 2018.






Source link