|
| 1 | +--- |
| 2 | +mainImage: ../../../images/part-4.svg |
| 3 | +part: 4 |
| 4 | +letter: a |
| 5 | +lang: fr |
| 6 | +--- |
| 7 | + |
| 8 | +<div class="content"> |
| 9 | + |
| 10 | +Poursuivons notre travail sur le backend de l'application de notes que nous avons commencé dans la [partie 3](/fr/part3). |
| 11 | + |
| 12 | +### Structure du projet |
| 13 | +Avant de passer au sujet des tests, nous allons modifier la structure de notre projet pour respecter les meilleures pratiques de Node.js. |
| 14 | + |
| 15 | +AprÚs avoir apporté les modifications à la structure de répertoire de notre projet, nous obtenons la structure suivante : |
| 16 | + |
| 17 | +```bash |
| 18 | +âââ index.js |
| 19 | +âââ app.js |
| 20 | +âââ build |
| 21 | +â âââ ... |
| 22 | +âââ controllers |
| 23 | +â âââ notes.js |
| 24 | +âââ models |
| 25 | +â âââ note.js |
| 26 | +âââ package-lock.json |
| 27 | +âââ package.json |
| 28 | +âââ utils |
| 29 | +â âââ config.js |
| 30 | +â âââ logger.js |
| 31 | +â âââ middleware.js |
| 32 | +``` |
| 33 | + |
| 34 | +Jusqu'à présent, nous avons utilisé <i>console.log</i> et <i>console.error</i> pour afficher différentes informations du code. |
| 35 | +Cependant, ce n'est pas une trÚs bonne façon de faire les choses. |
| 36 | +Séparons toute l'impression dans la console de son propre module <i>utils/logger.js</i> : |
| 37 | + |
| 38 | +```js |
| 39 | +const info = (...params) => { |
| 40 | + console.log(...params) |
| 41 | +} |
| 42 | + |
| 43 | +const error = (...params) => { |
| 44 | + console.error(...params) |
| 45 | +} |
| 46 | + |
| 47 | +module.exports = { |
| 48 | + info, error |
| 49 | +} |
| 50 | +``` |
| 51 | +Le journalisateur dispose de deux fonctions, __info__ pour afficher les messages de journalisation normaux, et __error__ pour tous les messages d'erreur. |
| 52 | + |
| 53 | +Extraire la journalisation dans son propre module est une bonne idée à plus d'un titre. Si nous voulions commencer à écrire des journaux dans un fichier ou les envoyer à un service de journalisation externe comme [graylog] (https://www.graylog.org/) ou [papertrail] (https://papertrailapp.com), nous n'aurions à effectuer des modifications qu'à un seul endroit. |
| 54 | + |
| 55 | +Le contenu du fichier <i>index.js</i> utilisé pour démarrer l'application est simplifié comme suit : |
| 56 | + |
| 57 | +```js |
| 58 | +const app = require('./app') // l'application Express |
| 59 | +const config = require('./utils/config') |
| 60 | +const logger = require('./utils/logger') |
| 61 | + |
| 62 | +app.listen(config.PORT, () => { |
| 63 | + logger.info(`Server running on port ${config.PORT}`) |
| 64 | +}) |
| 65 | +``` |
| 66 | + |
| 67 | +Le fichier <i>index.js</i> n'importe que l'application réelle depuis le fichier <i>app.js</i>, puis démarre l'application. La fonction info du module journalisateur est utilisée pour l'affichage console indiquant que l'application est en cours d'exécution. |
| 68 | + |
| 69 | +Maintenant, l'application Express et le code qui s'occupe du serveur web sont sĂ©parĂ©s l'un de l'autre selon les [meilleures](https://dev.to/nermineslimane/always-separate-app-and-server-files--1nc7) [pratiques](https://nodejsbestpractices.com/sections/projectstructre/separateexpress). L'un des avantages de cette mĂ©thode est que l'application peut dĂ©sormais ĂȘtre testĂ©e au niveau des appels API HTTP sans effectuer rĂ©ellement des appels via HTTP sur le rĂ©seau, ce qui rend l'exĂ©cution des tests plus rapide. |
| 70 | + |
| 71 | +La gestion des variables d'environnement est extraite dans un fichier séparé <i>utils/config.js</i> : |
| 72 | + |
| 73 | +```js |
| 74 | +require('dotenv').config() |
| 75 | + |
| 76 | +const PORT = process.env.PORT |
| 77 | +const MONGODB_URI = process.env.MONGODB_URI |
| 78 | + |
| 79 | +module.exports = { |
| 80 | + MONGODB_URI, |
| 81 | + PORT |
| 82 | +} |
| 83 | + |
| 84 | +``` |
| 85 | + |
| 86 | +Les autres parties de l'application peuvent accéder aux variables d'environnement en important le module de configuration : |
| 87 | + |
| 88 | +```js |
| 89 | +const config = require('./utils/config') |
| 90 | + |
| 91 | +logger.info(`Serveur en cours d'exécution sur le port ${config.PORT}`) |
| 92 | + |
| 93 | +``` |
| 94 | + |
| 95 | +Les gestionnaires de routes ont également été déplacés dans un module dédié. Les gestionnaires d'événements des routes sont couramment appelés <i>contrÎleurs</i>, et c'est pourquoi nous avons créé un nouveau répertoire <i>controllers</i>. Toutes les routes liées aux notes sont désormais dans le module <i>notes.js</i> sous le répertoire <i>controllers</i>. |
| 96 | + |
| 97 | +Le contenu du module <i>notes.js</i> est le suivant : |
| 98 | + |
| 99 | +```js |
| 100 | +const notesRouter = require('express').Router() |
| 101 | +const Note = require('../models/note') |
| 102 | + |
| 103 | +notesRouter.get('/', (request, response) => { |
| 104 | + Note.find({}).then(notes => { |
| 105 | + response.json(notes) |
| 106 | + }) |
| 107 | +}) |
| 108 | + |
| 109 | +notesRouter.get('/:id', (request, response, next) => { |
| 110 | + Note.findById(request.params.id) |
| 111 | + .then(note => { |
| 112 | + if (note) { |
| 113 | + response.json(note) |
| 114 | + } else { |
| 115 | + response.status(404).end() |
| 116 | + } |
| 117 | + }) |
| 118 | + .catch(error => next(error)) |
| 119 | +}) |
| 120 | + |
| 121 | +notesRouter.post('/', (request, response, next) => { |
| 122 | + const body = request.body |
| 123 | + |
| 124 | + const note = new Note({ |
| 125 | + content: body.content, |
| 126 | + important: body.important || false, |
| 127 | + }) |
| 128 | + |
| 129 | + note.save() |
| 130 | + .then(savedNote => { |
| 131 | + response.json(savedNote) |
| 132 | + }) |
| 133 | + .catch(error => next(error)) |
| 134 | +}) |
| 135 | + |
| 136 | +notesRouter.delete('/:id', (request, response, next) => { |
| 137 | + Note.findByIdAndRemove(request.params.id) |
| 138 | + .then(() => { |
| 139 | + response.status(204).end() |
| 140 | + }) |
| 141 | + .catch(error => next(error)) |
| 142 | +}) |
| 143 | + |
| 144 | +notesRouter.put('/:id', (request, response, next) => { |
| 145 | + const body = request.body |
| 146 | + |
| 147 | + const note = { |
| 148 | + content: body.content, |
| 149 | + important: body.important, |
| 150 | + } |
| 151 | + |
| 152 | + Note.findByIdAndUpdate(request.params.id, note, { new: true }) |
| 153 | + .then(updatedNote => { |
| 154 | + response.json(updatedNote) |
| 155 | + }) |
| 156 | + .catch(error => next(error)) |
| 157 | +}) |
| 158 | + |
| 159 | +module.exports = notesRouter |
| 160 | +``` |
| 161 | + |
| 162 | +Ceci est presque une copie conforme de notre précédent fichier <i>index.js</i>. |
| 163 | + |
| 164 | +Cependant, il y a quelques changements importants. Au tout début du fichier, nous créons un nouvel objet [router](http://expressjs.com/en/api.html#router): |
| 165 | + |
| 166 | +```js |
| 167 | +const notesRouter = require('express').Router() |
| 168 | + |
| 169 | +//... |
| 170 | + |
| 171 | +module.exports = notesRouter |
| 172 | + |
| 173 | +``` |
| 174 | + |
| 175 | +Le module exporte le router pour qu'il soit disponible pour tous les consommateurs du module. |
| 176 | + |
| 177 | +Toutes les routes sont maintenant dĂ©finies pour l'objet router, de la mĂȘme maniĂšre que ce que nous avons fait auparavant avec l'objet reprĂ©sentant l'ensemble de l'application. |
| 178 | + |
| 179 | +Il convient de noter que les chemins dans les gestionnaires de routes ont été raccourcis. Dans la version précédente, nous avions : |
| 180 | + |
| 181 | +```js |
| 182 | +app.delete('/api/notes/:id', (request, response) => { |
| 183 | + |
| 184 | +``` |
| 185 | +
|
| 186 | +Et dans la version actuelle, nous avons : |
| 187 | +
|
| 188 | +```js |
| 189 | +notesRouter.delete('/:id', (request, response) => { |
| 190 | + |
| 191 | +``` |
| 192 | +Alors qu'est-ce que ces objets router exactement ? Le manuel Express fournit l'explication suivante : |
| 193 | +
|
| 194 | +> <i>Un objet router est une instance isolée de middleware et de routes. Vous pouvez le considérer comme une "mini-application", capable uniquement de réaliser des fonctions de middleware et de routage. Chaque application Express a un router d'application intégré.</i> |
| 195 | +
|
| 196 | +Le router est en fait un <i>middleware</i>, qui peut ĂȘtre utilisĂ© pour dĂ©finir des "routes associĂ©es" en un seul endroit, qui est gĂ©nĂ©ralement placĂ© dans son propre module. |
| 197 | +
|
| 198 | +Le fichier <i>app.js</i> qui crée l'application réelle prend le router en compte comme indiqué ci-dessous: |
| 199 | +
|
| 200 | +```js |
| 201 | +const notesRouter = require('./controllers/notes') |
| 202 | +app.use('/api/notes', notesRouter) |
| 203 | +``` |
| 204 | +
|
| 205 | +Le routeur que nous avons dĂ©fini prĂ©cĂ©demment est utilisĂ© si l'URL de la requĂȘte commence par <i>/api/notes</i>. Pour cette raison, l'objet notesRouter doit seulement dĂ©finir les parties relatives des routes, c'est-Ă -dire le chemin vide <i>/</i> ou simplement le paramĂštre <i>/:id</i>. |
| 206 | +
|
| 207 | +AprÚs avoir effectué ces modifications, notre fichier <i>app.js</i> ressemble à ceci: |
| 208 | +
|
| 209 | +```js |
| 210 | +const config = require('./utils/config') |
| 211 | +const express = require('express') |
| 212 | +const app = express() |
| 213 | +const cors = require('cors') |
| 214 | +const notesRouter = require('./controllers/notes') |
| 215 | +const middleware = require('./utils/middleware') |
| 216 | +const logger = require('./utils/logger') |
| 217 | +const mongoose = require('mongoose') |
| 218 | + |
| 219 | +mongoose.set('strictQuery', false) |
| 220 | + |
| 221 | +logger.info('connecting to', config.MONGODB_URI) |
| 222 | + |
| 223 | +mongoose.connect(config.MONGODB_URI) |
| 224 | + .then(() => { |
| 225 | + logger.info('connected to MongoDB') |
| 226 | + }) |
| 227 | + .catch((error) => { |
| 228 | + logger.error('error connecting to MongoDB:', error.message) |
| 229 | + }) |
| 230 | + |
| 231 | +app.use(cors()) |
| 232 | +app.use(express.static('build')) |
| 233 | +app.use(express.json()) |
| 234 | +app.use(middleware.requestLogger) |
| 235 | + |
| 236 | +app.use('/api/notes', notesRouter) |
| 237 | + |
| 238 | +app.use(middleware.unknownEndpoint) |
| 239 | +app.use(middleware.errorHandler) |
| 240 | + |
| 241 | +module.exports = app |
| 242 | +``` |
| 243 | +
|
| 244 | +Le fichier utilise différents middlewares, dont l'un est le <i>notesRouter</i> qui est attaché à la route <i>/api/notes</i>. |
| 245 | +
|
| 246 | +Notre middleware personnalisé a été déplacé vers un nouveau module <i>utils/middleware.js</i> : |
| 247 | +
|
| 248 | +```js |
| 249 | +const logger = require('./logger') |
| 250 | + |
| 251 | +const requestLogger = (request, response, next) => { |
| 252 | + logger.info('Method:', request.method) |
| 253 | + logger.info('Path: ', request.path) |
| 254 | + logger.info('Body: ', request.body) |
| 255 | + logger.info('---') |
| 256 | + next() |
| 257 | +} |
| 258 | + |
| 259 | +const unknownEndpoint = (request, response) => { |
| 260 | + response.status(404).send({ error: 'unknown endpoint' }) |
| 261 | +} |
| 262 | + |
| 263 | +const errorHandler = (error, request, response, next) => { |
| 264 | + logger.error(error.message) |
| 265 | + |
| 266 | + if (error.name === 'CastError') { |
| 267 | + return response.status(400).send({ error: 'malformatted id' }) |
| 268 | + } else if (error.name === 'ValidationError') { |
| 269 | + return response.status(400).json({ error: error.message }) |
| 270 | + } |
| 271 | + |
| 272 | + next(error) |
| 273 | +} |
| 274 | + |
| 275 | +module.exports = { |
| 276 | + requestLogger, |
| 277 | + unknownEndpoint, |
| 278 | + errorHandler |
| 279 | +} |
| 280 | +``` |
| 281 | +
|
| 282 | +La responsabilité d'établir la connexion à la base de données a été confiée au module <i>app.js</i>. Le fichier <i>note.js</i> dans le répertoire <i>models</i> ne définit que le schéma Mongoose pour les notes. |
| 283 | +
|
| 284 | +```js |
| 285 | +const mongoose = require('mongoose') |
| 286 | + |
| 287 | +const noteSchema = new mongoose.Schema({ |
| 288 | + content: { |
| 289 | + type: String, |
| 290 | + required: true, |
| 291 | + minlength: 5 |
| 292 | + }, |
| 293 | + important: Boolean, |
| 294 | +}) |
| 295 | + |
| 296 | +noteSchema.set('toJSON', { |
| 297 | + transform: (document, returnedObject) => { |
| 298 | + returnedObject.id = returnedObject._id.toString() |
| 299 | + delete returnedObject._id |
| 300 | + delete returnedObject.__v |
| 301 | + } |
| 302 | +}) |
| 303 | + |
| 304 | +module.exports = mongoose.model('Note', noteSchema) |
| 305 | +``` |
| 306 | +
|
| 307 | +En résumé, la structure du répertoire ressemble à ceci aprÚs les modifications apportées : |
| 308 | +
|
| 309 | +```bash |
| 310 | +âââ index.js |
| 311 | +âââ app.js |
| 312 | +âââ build |
| 313 | +â âââ ... |
| 314 | +âââ controllers |
| 315 | +â âââ notes.js |
| 316 | +âââ models |
| 317 | +â âââ note.js |
| 318 | +âââ package-lock.json |
| 319 | +âââ package.json |
| 320 | +âââ utils |
| 321 | +â âââ config.js |
| 322 | +â âââ logger.js |
| 323 | +â âââ middleware.js |
| 324 | +``` |
| 325 | +
|
| 326 | +Pour les applications plus petites, la structure n'a pas une grande importance. Une fois que l'application commence à devenir plus grande, il faut établir une certaine structure et séparer les différentes responsabilités de l'application en modules séparés. Cela facilitera grandement le développement de l'application. |
| 327 | +
|
| 328 | +Il n'y a pas de structure de répertoire stricte ou de convention de nommage de fichiers requise pour les applications Express. En revanche, Ruby on Rails nécessite une structure spécifique. Notre structure actuelle suit simplement certaines des meilleures pratiques que vous pouvez trouver sur Internet. |
| 329 | +
|
| 330 | +Vous pouvez trouver le code de notre application actuelle dans son intégralité dans la branche <i>part4-1</i> de ce [dépÎt GitHub](https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-1). |
| 331 | +
|
| 332 | +Si vous clonez le projet pour vous-mĂȘme, exĂ©cutez la commande npm install avant de lancer l'application avec npm start. |
| 333 | +
|
| 334 | +</div> |
| 335 | +
|
| 336 | +### Notes Ă l'export |
| 337 | +
|
0 commit comments