###### tags: `Nodejs` `React` `MongoDB` `Mongoose` `Express` # Créer une application web avec son api ## 0. Prérequis * Installer la dernière version de NPM sur son poste -npx en fait parti) ## 1. Créer le projet 1. Créer un nouveau repertoire qui va contenir le projet. Lui donner le nom de son projet. 2. Créer un sous-repertoire. L'appeler api. 3. Créer un nouveau repertoire dans projet/api/. 4. Se placer dans ce dernier reperoire et créer une nouvelle application React : ```console cd projet/api npx create-react-app client ``` 5. Revenir dans le dossier projet/api et initialiser le projet : ```console cd projet/api npm init -y ``` 6. Ajouter les dépendances : ```console cd projet/api npm install express body-parser cors mongoose nodemon ``` A la fin de ces étapes, voici l'architecture du projet : ``` > projet | > api | | > client | | | ... | | > nodes_modules | | | ... | | - package-lock.json | | - package.json ``` Aller dans le fichier api/package.json et obersver la partie scripts. Si vous n'avez pas de scripts, ajoutez la ligne suivante : ```json= "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "node server.js" }, ``` ## 2. Mettre en place le server 1. Ajouter un fichier server.js dans le dossier projet/api. ```jsx= // api/server.js const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const path = require('path'); const app = express(); const apiPort = 3080; app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, 'client/build'))); app.get('/api', (req, res) => { res.send('Hello World!'); }); app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`)); ``` 2. Lancer le server : ```console cd projet/api npm run dev ``` Cette commande devrait afficher le message suisvant : ```console Server running on port 3080 ``` Si ce n'est pas le cas, il y a un problème. ## 3. Se connecter à l'api ### a. Création de l'api 1. Aller sur le site de [MongoDB](https://www.mongodb.com/fr) et se créer un compte. 2. Créer un nouveau cluster si vous n'en avaez pas en suivant les instructions. 3. Créer une nouvelle base de données en suivant les instructions (il est obligatoire de créer une collection pour créer une base de données, on pourra la modifier par la suite) ### b. Connection à l'api 1. Créer un repertoire db dans le dossier projet/api. 2. Ajouter un fichier index.js en modifiant les variables : ```jsx= // db/index.js const mongoose = require('mongoose'); // Database Name const clusterName = 'cluster0.yjiu3.mongodb.net'; const dbName = 'new-db'; const dbUser = 'dbUser'; const dbPwd = 'MOTDEPASSE'; // Connection URL const url = 'mongodb+srv://' + dbUser + ':' + dbPwd + '@' + clusterName + '/' + dbName + '?retryWrites=true&w=majority'; mongoose.connect(url, { useNewUrlParser: true, useUnifiedTopology: true, }); const db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function () { // we're connected! }); module.exports = db; ``` 3. Ajouter la connection au fichier de configuration server.js avec les lignes suivantes : ```jsx= const db = require('./db'); db.on('error', console.error.bind(console, 'MongoDB connection error:')); ``` Voici le server.js après les modifications : ```jsx= // server.js const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const path = require('path'); const app = express(); const apiPort = 3080; const db = require('./db'); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, 'client/build'))); db.on('error', console.error.bind(console, 'MongoDB connection error:')); app.get('/api', (req, res) => { res.send('Hello World!'); }); app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`)); ``` A la fin de ces étapes, voici l'architecture du projet : ``` > projet | > api | | > client | | | ... | | > db | | | - index.js | | > node_modules | | | ... | | - package-lock.json | | - package.json | | - server.js ``` Relancer le server pour vérifier que tout va bien : ```console cd projet/api npm run dev ``` Cette commande devrait afficher le message suisvant : ```console Server running on port 3080 ``` Si ce n'est pas le cas, il y a un problème. ## 4. Construire l'api ### a. Ajouter des collections dans la base de données Aller sur le tableau de bord de MongoDB Atlas. Accéder à la base de donnée. Créer une nouvelle collection. ### b. Créer les collections dans l'application 1. Créer un nouveau repertoire models dans projet/api. 2. Créer un nouveau fichier UserModel.js : ```jsx= // api/models/UserModel.js var mongoose = require('mongoose'); //Define a schema var Schema = mongoose.Schema; var UserSchema = new Schema( { name: String, }, { timestamps: true } ); //Export function to create "User" model class module.exports = mongoose.model('User', UserSchema); ``` ### c. Utiliser les collections #### Controller 1. Créer un nouveau repertoire controllers dans projet/api. 2. Créer un nouveau fichier UserController.js : ```jsx=x= // api/controllers/UserController.js const User = require('../models/UserModel'); createUser = (req, res) => { const body = req.body; if (!body) { return res.status(400).json({ success: false, error: 'You must provide a user', }); } const user = new User(body); if (!user) { return res.status(400).json({ success: false, error: err }); } user .save() .then(() => { return res.status(201).json({ success: true, id: user._id, message: 'user created!', }); }) .catch((error) => { return res.status(400).json({ error, message: 'user not created!', }); }); }; updateUser = async (req, res) => { const body = req.body; if (!body) { return res.status(400).json({ success: false, error: 'You must provide a body to update', }); } User.findOne({ _id: req.params.id }, (err, user) => { if (err) { return res.status(404).json({ err, message: 'user not found!', }); } user.name = body.name; user .save() .then(() => { return res.status(200).json({ success: true, id: user._id, message: 'user updated!', }); }) .catch((error) => { return res.status(404).json({ error, message: 'user not updated!', }); }); }); }; deleteUser = async (req, res) => { await User.findOneAndDelete({ _id: req.params.id }, (err, user) => { if (err) { return res.status(400).json({ success: false, error: err }); } if (!user) { return res.status(404).json({ success: false, error: `user not found` }); } return res.status(200).json({ success: true, data: user }); }).catch((err) => console.log(err)); }; getUserById = async (req, res) => { await User.findOne({ _id: req.params.id }, (err, user) => { if (err) { return res.status(400).json({ success: false, error: err }); } if (!user) { return res.status(404).json({ success: false, error: `user not found` }); } return res.status(200).json({ success: true, data: user }); }).catch((err) => console.log(err)); }; getUsers = async (req, res) => { await User.find({}, (err, users) => { if (err) { return res.status(400).json({ success: false, error: err }); } if (!users.length) { return res.status(404).json({ success: false, error: `user not found` }); } return res.status(200).json({ success: true, data: users }); }).catch((err) => console.log(err)); }; module.exports = { createUser, updateUser, deleteUser, getUsers, getUserById, }; ``` #### Routes 1. Créer un nouveau repertoire routes dans projet/api. 2. Créer un nouveau fichier UserRouter.js : ```jsx= // api/routes/UserRouter.js const express = require('express'); const UserController = require('../controllers/UserController'); const router = express.Router(); router.post('/', UserController.createUser); router.put('/:id', UserController.updateUser); router.delete('/:id', UserController.deleteUser); router.get('/:id', UserController.getUserById); router.get('/', UserController.getUsers); module.exports = router; ``` #### Server 1. Ajouter les lignes suivantes dans le server.js : ```jsx= const UserRouter = require('./routes/UserRouter'); app.use('/api/user', UserRouter); ``` Voici le server.js après les modifications : ```jsx= // api/server.js const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const path = require('path'); const app = express(); const apiPort = 3080; const db = require('./db'); const UserRouter = require('./routes/UserRouter'); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, 'client/build'))); db.on('error', console.error.bind(console, 'MongoDB connection error:')); app.get('/api', (req, res) => { res.send('Hello World!'); }); app.use('/api/user', UserRouter); app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`)); ``` A la fin de ces étapes, voici l'architecture du projet : ``` > projet | > api | | > client | | | ... | | > controllers | | | - UserController.js | | > db | | | - index.js | | > models | | | - UserModel.js | | > node_modules | | | ... | | > routes | | | - UserRouter.js | | - package-lock.json | | - package.json | | - server.js ``` On peut de nouveau tester de lancer le server. ## 5. Tester les requêtes Pour tester les requêtes, on peut utiliser [Postman](https://www.postman.com/). Il est possible d'utilise rla version online si vous avez l'agent postman dans votre toolbar. Pour cela, il faut lancer le server nodeJs : ```console npm run dev ``` Et tester par exemple l'url suivante dans Postman en GET : > localhost:3080/api/user Il est normal que le retour soit vide puisqu'on a rien ajouté dans la collection. On va le faire tout de suite. Mais si déjà il n'y a pas d'erreur, alors c'est cool. ## 6. Ajouter le front ### a. Créer les pages du CRUD Exécuter la commande suivante pour ajouter les dépendances nécessaires : ```console cd api/client npm install styled-components react-table react-router-dom axios bootstrap --save ``` Voici les quelques fichiers de base en React. Pour comprendre le React, aller sur la [documentation](https://fr.reactjs.org/docs/getting-started.html). Pour rappel, j'utilise les Hooks. A la fin de ces étapes, voici l'architecture du projet : ``` > projet | > api | | ... | | > client | | | ... | | | > src | | | | ... | | | | - App.js | | | | > pages | | | | | - index.js | | | | | - UserInsert.js | | | | | - UserList.js | | | | | - UserUpdate.js | | | | > components | | | | | - Header.js | | | | | - Navbar.js ``` Un header : ```jsx= // client/src/components/Header.js import React from 'react'; import { Navbar } from './Navbar'; export const Header = () => { return ( <div className='header'> <h1>React With NodeJS</h1> <Navbar /> </div> ); }; ``` La navbar : ```jsx= // client/src/components/Navbar.js import React from 'react'; import { Link } from 'react-router-dom'; export const Navbar = () => { return ( <div className='Navbar'> <Link to='/users/list' className='nav-link'> List Users </Link> <Link to='/users/create' className='nav-link'> Create User </Link> </div> ); }; export default Navbar; ``` Le fichier App.js est la page par défaut : ```jsx= // client/src/App.js import React, { Component } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import './App.css'; import { Header } from './components/Header'; import { UsersList, UsersInsert, UsersUpdate } from './pages/index'; class App extends Component { render() { return ( <div className='App'> <Router> <Header></Header> <Switch> <Route path='/users/list' exact component={UsersList} /> <Route path='/users/create' exact component={UsersInsert} /> <Route path='/users/update/:id' exact component={UsersUpdate} /> </Switch> </Router> </div> ); } } export default App; ``` Ci-dessous les pages que l'on va utiliser : ```jsx= // src/pages/UsersInsert.js import React, { Component } from 'react' const UsersInsert = () => { return ( <div className='UsersInsert'> <p>Insert</p> </div> ); }; export default UsersInsert ``` ```jsx= // src/pages/UsersUpdate.js import React, { Component } from 'react' const UsersUpdate = () => { return ( <div className='UsersUpdate'> <p>Update</p> </div> ); }; export default UsersUpdate ``` ```jsx= // src/pages/UsersList.js import React, { Component } from 'react' const UsersList = () => { return ( <div className='UsersList'> <p>List</p> </div> ); }; export default UsersList ``` ```jsx= // src/pages/index.js import UsersList from './UsersList'; import UsersInsert from './UsersInsert'; import UsersUpdate from './UsersUpdate'; export { UsersList, UsersInsert, UsersUpdate }; ``` Pour lancer le front, éxecuter la commande suivante : ```console cd api/client npm start ``` ### b. Récupérer les données de l'api 1. Créer un nouveau repertoire api dans client/src. 2. Créer un nouveau fichier index.js dans ce nouveau dossier : ```jsx= // client/src/api/index.js import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:3080/api', }); export const insertUser = (payload) => api.post(`/user`, payload); export const getAllUsers = () => api.get(`/user`); export const updateUserById = (id, payload) => api.put(`/user/${id}`, payload); export const deleteUserById = (id) => api.delete(`/user/${id}`); export const getUserById = (id) => api.get(`/user/${id}`); const apis = { insertUser, getAllUsers, updateUserById, deleteUserById, getUserById, }; export default apis; ``` A la fin de cette étape, voici l'architecture du projet : ``` > projet | > api | | ... | | > client | | | ... | | | > src | | | | ... | | | | - App.js | | | | > api | | | | | - index.js | | | | > pages | | | | | ... | | | | > components | | | | | ... ``` ### c. Afficher une liste Dans un premier temps, on va juste afficher les utilisateurs dans les logs d'un navigateur. Pour cela, on va modifier le fichier UsersList.js : ```jsx= // src/pages.UsersList.js import React, { useState, useEffect } from 'react'; import apis from '../api/index'; const UsersList = () => { const [users, setUsers] = useState(); const [isLoading, setIsloading] = useState(true); useEffect(() => { // METHODE 1 : nouvelles bonnes pratiques const loadData = async () => { const users = await apis.getAllUsers(); setUsers(users); setIsloading(false); }; loadData(); // METHODE 2 : un peu plus fastidieux s'il y a beaucoup d'instructions apis.getAllUsers().then((users) => { setUsers(users); setIsloading(false); }); }, []); console.log(users); return ( <div className='UsersList'> <p>List</p> </div> ); }; export default UsersList; ``` Lancer l'application React. Cliquer sur le lien pour voir la liste des utilisateurs (http://localhost:3000/users/list) et afficher les log (F12). On doit voir un object avec plusieurs entités data. Pour l'instant, c'est vide. On va ajouter un utilisateur. ### d. Ajouter On va modifier le fichier UsersInsert.js : ```jsx= // src/pages/UsersInsert.js import React, { useState } from 'react'; import apis from '../api/index'; import styled from 'styled-components'; const Title = styled.h1.attrs({ className: 'h1', })``; const Wrapper = styled.div.attrs({ className: 'form-group', })` margin: 0 30px; `; const Label = styled.label` margin: 5px; `; const InputText = styled.input.attrs({ className: 'form-control', })` margin: 5px; `; const Button = styled.button.attrs({ className: `btn btn-primary`, })` margin: 15px 15px 15px 5px; `; const CancelButton = styled.a.attrs({ className: `btn btn-danger`, })` margin: 15px 15px 15px 5px; `; const UsersInsert = () => { const [name, setName] = useState(); const handleChangeInputName = async (event) => { const name = event.target.value; setName(name); }; const handleIncludeUser = async () => { const res = apis.insertUser({ name }); window.alert(`User inserted successfully`); setName(name); }; return ( <div className='UsersInsert'> <Wrapper> <Title>Create User</Title> <Label>Name: </Label> <InputText type='text' onChange={handleChangeInputName} /> <Button onClick={handleIncludeUser}>Add User</Button> <CancelButton href={'/users/list'}>Cancel</CancelButton> </Wrapper> </div> ); }; export default UsersInsert; ``` Tester l'ajout d'un utilisateur, puis retourner sur la page de la liste. On devrait voir un nouvel utilisateur dans l'objet retourné. ### e. Modifier On va modifier le fichier UsersUpdate.js : ```jsx= // src/pages/UsersUpdate.js import React, { useState, useEffect } from 'react'; import apis from '../api/index'; import styled from 'styled-components'; import { useParams } from 'react-router-dom'; const Title = styled.h1.attrs({ className: 'h1', })``; const Wrapper = styled.div.attrs({ className: 'form-group', })` margin: 0 30px; `; const Label = styled.label` margin: 5px; `; const InputText = styled.input.attrs({ className: 'form-control', })` margin: 5px; `; const Button = styled.button.attrs({ className: `btn btn-primary`, })` margin: 15px 15px 15px 5px; `; const CancelButton = styled.a.attrs({ className: `btn btn-danger`, })` margin: 15px 15px 15px 5px; `; const UsersUpdate = () => { const { id } = useParams(); const [name, setName] = useState(); const [user, setUser] = useState(); const [isLoading, setIsloading] = useState(true); useEffect(() => { const loadData = async () => { await apis.getUserById(id).then((user) => { setUser(user); setName(user.data.data.name); setIsloading(false); }); }; loadData(); }, []); const handleChangeInputName = async (event) => { const name = event.target.value; setName(name); }; const handleUpdateUser = async () => { await apis.updateUserById(id, { name }).then((res) => { window.alert(`User updated successfully`); }); }; return ( <div className='UsersUpdate'> <h1>In this page you'll see the form to update a user</h1> <Wrapper> <Title>Update User</Title> <Label>Name: </Label> <InputText type='text' value={name} onChange={handleChangeInputName} /> <Button onClick={handleUpdateUser}>Update User</Button> <CancelButton href={'/users/list'}>Cancel</CancelButton> </Wrapper> </div> ); }; export default UsersUpdate; ``` Pour tester la modification, afficher la liste des utilisateurs avec Postamn. Récupérer l'id d'un utilisateur. Taper l'url de modification dans le navigateur (http://localhost:3000/users/update/id). Et voilà ! [Ce tuto](https://medium.com/swlh/how-to-create-your-first-mern-mongodb-express-js-react-js-and-node-js-stack-7e8b20463e66) est aussi très complet, il développe d'avantage les étapes, et montre également comment supprimer. Bisous <3