###### 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