Tempo di lettura: 7 minuti
Continua il tutorial dedicato a React e NodeJs relativo alla realizzazione del backend dell’applicazione Heroes, se vi siete persi i primi potete trovarli qui.
React e NodeJs: Struttura del progetto
Prima di passare al test dell’applicazione , modifichiamone la struttura in modo tale da essere in linea con la best practice di NodeJs.
Una volta modificato la struttura dello stesso si presenterà nella seguente maniera:
├── index.js
├── app.js
├── build
│ └── ...
├── controllers
│ └── heroes.js
├── models
│ └── note.js
├── package-lock.json
├── package.json
├── utils
│ ├── config.js
│ ├── logger.js
│ └── middleware.js
React e NodeJs: il file Logger.js
Fino adesso abbiamo utilizzato diversi console.log() e console.error() sparsi in tutto il nostro codice. Ciò non è una buona pratica, difatti andremo ad includerli in un file separato, nella root principale del progetto create la directory con file utils/logger.js ed inserite:
const info = (...params) => { console.log(...params) } const error = (...params) => { console.error(...params) } module.exports = { info, error }
Il Logger ,fondamentalmente, ha due funzioni:
- Informazioni per la stampa dei normali messaggi di registro
- Errore per quanto riguarda i messaggi di errore
Avere dei file separati i quali svolgono ognuno il prorpio compito è una buona pratica , difatti se volessimo iniziare a scrivere i log in un file o inviarli ad un servizio di registrazione esterno come graylog o papertrail, dovremmo apportare delle modifiche solo in un posto senza impazzire.
Modifica ora il contenuto del file index.js nella seguente maniera:
const app = require('./app') // the actual Express application const http = require('http') const config = require('./utils/config') const logger = require('./utils/logger') const server = http.createServer(app) server.listen(config.PORT, () => { logger.info(`Server running on port ${config.PORT}`) })
come potete notare lo abbiamo semplificato notevolmente. Infatti questi ora importa solamente l’applicazione effettiva dal file app.js e quindi avvia l’applicazione.
Le info sulla funzione del modulo logger vengono utilizzate per la stampa che ci avvisa dell’avvenuta esecuzione dell’applicazione.
Il file config.js
La gestione delle variabili d’ambiente viene ora gestita dal file utils/config.js separato, quindi createlo e digitate:
require('dotenv').config() const PORT = process.env.PORT const MONGODB_URI = process.env.MONGODB_URI module.exports = { MONGODB_URI, PORT }
Le altre parti dell’applicazione possono accedere alle variabili d’ambiente importando il modulo di configurazione:
const config = require('./utils/config') logger.info(`Server running on port ${config.PORT}`)
File heroes.js del controllers
Anche i gestori delle rotte sono stati spostati in un modulo dedicato. Questi sono comunemente indicati come controller, per tale motivo andremo a creare nelle root principale del progetto una directory chiamata controller.
All’interno di essa realizzate il file heroes.js e digitate:
controller/heroes.js
const heroesRouter = require('express').Router() const Hero = require('../models/hero') heroesRouter.get('/', (request, response) => { Hero.find({}).then(heroes => { response.json(heroes) }) }) heroesRouter.get('/:id', (request, response, next) => { Hero.findById(request.params.id).then(h =>{ if(h){ response.json(h) }else { response.status(404).end() } }) .catch(error => next(error)) }) heroesRouter.post('/', (request, response) => { const body = request.body if(body.name === undefined){ return response.status(404).json({ error: 'Contenuto vuoto' }) } const hero = new Hero({ name: body.name, important: body.important || false, date: new Date() }) hero.save().then(savedHeroe => { response.json(savedHeroe) }) }) heroesRouter.delete('/:id', (request, response, next) => { Hero.findByIdAndRemove(request.params.id) .then(result => { response.status(204).end() }) .catch(error => next(error)) }) heroesRouter.put('/:id', (request, response, next) => { const body = request.body const hero = { name: body.name, important: body.important, } Hero.findByIdAndUpdate(request.params.id, hero, { new: true }) .then(updatedHero => { response.json(updatedHero) }) .catch(error => next(error)) }) module.exports = heroesRouter
in effetti è molto simile al ,precedente file index.js. Tuttavia ci sono delle differenze significative. All’inizio creiamo un nuovo oggetto router:
const heroesRouter = require('express').Router() //... module.exports = heroesRouter
Il modulo viene esportato in modo tale da renderlo disponibile anche agli altri file.
Tutte le rotte sono ora definite per l’oggetto router, in modo simile a quello che avevamo fatto in precedenza con l’oggetto che rappresentava l’intera applicazione.
Vale ,anche , la pena notare che i metodi che gestiscono le rotte i sono accorciati:
da così:
app.delete('/api/heroes/:id', (request, response) => {...
a così:
heroesRouter.delete('/:id', (request, response...
Il router
Quindi diamo la definizione degli oggetti router. Secondo il manuale express:
Un oggetto router è un’istanza isolata di middleware e route. Puoi pensarla come una “mini-applicazione”, in grado solo di eseguire funzioni di middleware e routing. Ogni applicazione Express dispone di un app router integrato.
Il router è un middleware il quale viene utilizzato per la definizione di rotte correlate in un unico file.
Il file app.js, file principale dell’applicazione, utilizza il router come di suguito:
const notesRouter = require('./controllers/notes') app.use('/api/heroes', heroesRouter)
Il router qui utilizzato viene utilizzato se l’URL della request inizia con /api/heroes. Per tale motivo l’oggetto heroesRouter deve solo definire le parti relative delle rotte, ovvero il percorso vuoto / o il parametro /:id.
Quindi ,nella root principale del progetto create il file app.js e scrivete:
const config = require('./utils/config') const express = require('express') const app = express() const cors = require('cors') const notesRouter = require('./controllers/notes') const middleware = require('./utils/middleware') const logger = require('./utils/logger') const mongoose = require('mongoose') logger.info('connecting to', config.MONGODB_URI) mongoose.connect(config.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true }) .then(() => { logger.info('connected to MongoDB') }) .catch((error) => { logger.error('error connecting to MongoDB:', error.message) }) app.use(cors()) app.use(express.static('build')) app.use(express.json()) app.use(middleware.requestLogger) app.use('/api/notes', notesRouter) app.use(middleware.unknownEndpoint) app.use(middleware.errorHandler) module.exports = app
Il middleware
Il file utilizza un middleware diverso ed uno di questi è heroesRouter allegato alla route /api/heroes.
Allora create il file middleware.js all’interno della root utils ed inserite:
utils/middleware.js
const logger = require('./logger') const requestLogger = (request, response, next) => { logger.info('Method:', request.method) logger.info('Path: ', request.path) logger.info('Body: ', request.body) logger.info('---') next() } const unknownEndpoint = (request, response) => { response.status(404).send({ error: 'unknown endpoint' }) } const errorHandler = (error, request, response, next) => { logger.error(error.message) if (error.name === 'CastError') { return response.status(400).send({ error: 'malformatted id' }) } else if (error.name === 'ValidationError') { return response.status(400).json({ error: error.message }) } next(error) } module.exports = { requestLogger, unknownEndpoint, errorHandler }
La responsabilità di stabilire la connessione al database è del file app.js. Il file hero.js all’interno della directory models definisce solo lo schema Mongoose per gli eroi.
models/hero.js:
const mongoose = require('mongoose') const heroSchema = new mongoose.Schema({ name: { type: String, required: true, minlength: 5 }, date: { type: Date, required: true, }, important: Boolean, }) heroSchema.set('toJSON', { transform: (document, returnedObject) => { returnedObject.id = returnedObject._id.toString() delete returnedObject._id delete returnedObject.__v } }) module.exports = mongoose.model('Hero', heroSchema)
React e NodeJs strutturare il backend: Riepilogo
Ricapitolando , la struttura della directory aggiornata ora è simile alla seguente:
├── index.js
├── app.js
├── build
│ └── ...
├── controllers
│ └── heroes.js
├── models
│ └── hero.js
├── package-lock.json
├── package.json
├── utils
│ ├── config.js
│ ├── logger.js
│ └── middleware.js
Per le applicazioni più piccole la struttura non ha molta importanza. Una volta che l’applicazione inizia a crescere di dimensioni, dovrai stabilire un qualche tipo di struttura e separare le diverse responsabilità dell’applicazione in moduli separati. Ciò renderà lo sviluppo dell’applicazione molto più semplice.
Non esiste una struttura di directory rigida o una convenzione di denominazione dei file richiesta per le applicazioni Express. La nostra struttura attuale segue semplicemente alcune delle migliori pratiche che puoi trovare su Internet.
Puoi trovare il codice per la nostra attuale applicazione nella sua interezza nel ramo part_5.1 di questo repository Github .
Se cloni il progetto per te stesso, esegui il comando npm install prima di avviare l’applicazione con npm start .
Se volete Approfondire le conoscenze su Git e GIT hub potete seguire il corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!