Come costruire un’API Rest con NodeJS, Express e MySQL

Con la conoscenza di JavaScript e MySQL, possiamo costruire la nostra API NodeJS usando Express.

Ho fatto qualche ricerca, e stavo cercando di sviluppare un’API da zero.
Mi piace semplificare le cose e cercare di evitare la duplicazione del codice.

Questa guida vi mostrerà come costruire un’API da zero:
Imparerete come creare rotte,
come usare mysql2, come configurare e connettersi al database, e come eseguire query con istruzioni preparate.
Come creare un middleware che può ottenere un argomento aggiuntivo oltre a req, res e next callback.
Imparerete come controllare i dati dall’oggetto richiesta usando il modulo Express Validator.
Apprenderete come usare il modulo JWT per creare un token per l’utente, verificare il token e ottenere l’oggetto memorizzato nel token.
Inoltre, imparerete come fornire agli utenti il permesso di accedere a una certa rotta in base ai loro ruoli utente.

Tecnologie e pacchetti:

  • NodeJS
  • Express
  • mysql2
  • bcryptjs
  • jsonwebtoken
  • express-validator
  • dotenv
  • cors

Installazione di MySQL:

Io uso WSL, e puoi usare questo tutorial per vedere come installare MySQL in WSL.
Devi assicurarti che MySQL sia in esecuzione con questo comando:

sudo service mysql status
Entra in modalità schermo intero Esci dalla modalità schermo intero

Se non è in esecuzione, usa solo:

sudo service mysql start
Entra in modalità schermo intero Esci dalla modalità schermo intero

Panoramica delle applicazioni:

Costruiremo una rest API per le operazioni CRUD: creare, leggere, aggiornare e cancellare gli utenti.

Creare la cartella del progetto e installare tutte le dipendenze:

mkdir mysql-node-express && cd mysql-node-expressnpm init -ynpm i express express-validator mysql2 cors dotenv jsonwebtoken -Snpm i nodemon -D
Entrare in modalità schermo intero Uscire dalla modalità schermo intero

Andare al file package.json e cambiare il valore “main” in “src/server.js” e aggiungi questi script all’oggetto scripts:

"start": "node src/server.js","dev": "nodemon"
Entra in modalità schermo intero Esci dalla modalità schermo intero

package.json dovrebbe avere questo aspetto:

Crea il file .env:

Utilizzeremo il file .env per gestire tutte le nostre variabili d’ambiente.
Il file .env è un file nascosto che ci permette di personalizzare le nostre variabili d’ambiente usando la sintassi ENV VARIABLE = VALUE.
Queste variabili sono caricate utilizzando il modulo dotenv che abbiamo già installato.
Il file .env può essere definito in diversi stadi dell’ambiente (sviluppo / stage / ambienti di produzione).

Crea il file .env, copia le seguenti righe, e aggiorna il file con il tuo db_name, db_username, e password di MySQL:

# DB ConfigurationsHOST=localhostDB_USER=db_usernameDB_PASS=db_passwordDB_DATABASE=db_name# local runtime configsPORT=3000SECRET_JWT=supersecret
Entra nella modalità a schermo intero Esci dalla modalità a schermo intero

Crea il file nodemon.json:

Nodemon è uno strumento che aiuta a sviluppare applicazioni basate su node.js riavviando automaticamente l’applicazione node quando vengono rilevati cambiamenti di file nella directory di destinazione.
Il nodemon è un wrapper sostitutivo per node. Invece di usare il comando node, dovremmo usare il comando nodemon sulla linea di comando per eseguire il nostro script.
Possiamo facilmente aggiungere interruttori di configurazione mentre eseguiamo nodemon sulla linea di comando, come:

nodemon --watch src
Entrare nella modalità a schermo intero Uscire dalla modalità a schermo intero

Possiamo anche usare un file (nodemon.json) per specificare tutti gli interruttori.
Se vogliamo guardare diversi file in una directory, possiamo aggiungere la directory nell’array “watch”.
Se vogliamo cercare una particolare estensione (come un file ts) possiamo usare la proprietà “ext”.
Se vogliamo ignorare alcuni file, possiamo definirli nell’array “ignore”‘, e così via…
Io uso questo file soprattutto quando sto creando un server con NodeJS basato su typescript, ma penso sia più facile avere più posti per includere le configurazioni della nostra app.
Questo file è opzionale.

Crea il file nodemon.json e aggiungi questo al file:

{ "watch": , "ext": ".js", "ignore": }
Entra in modalità schermo intero Esci dalla modalità schermo intero

Crea la cartella src:

mkdir src && cd src
Entrare in modalità schermo intero Uscire dalla modalità schermo intero

Nella cartella src creare sottocartelle: controllers, models, routes, middleware, db, e utils:

mkdir controllers models routes middleware db utils
Entra in modalità schermo intero Esci dalla modalità schermo intero

Imposta il server Express:

Nella directory src creare il file server.js e copiare queste linee:

In questo file, importiamo express per costruire le rest API e usiamo express.json() per analizzare le richieste in arrivo con payload JSON.

Importiamo anche il modulo dotenv per leggere il file .env config file per ottenere il numero di porta per eseguire il server.

Importiamo anche userRouter.

Dopo di che, abbiamo un middleware che gestisce gli errori 404 → se qualcuno cerca un endpoint che non esiste, otterrà questo errore: ‘Endpoint Not Found’ con il codice di stato 404. Dopo di che, stiamo usando un middleware di errore che otterrà i dati di errore dai percorsi precedenti. se next(err) è chiamato, puoi vedere il middleware 404 come esempio.
Ascoltiamo la porta dal file.env e stampiamo sulla console che il server è in esecuzione.

Creiamo il database MySQL e la tabella utenti:

Nella directory db, creeremo il file create-user-db.sql e copiamo-incolliamo queste righe:

In questo script, prima eliminiamo il database se esiste in modo da poterlo resettare velocemente in caso di errore (potete commentare questa linea se volete), poi, creiamo il database se non esiste. Lo impostiamo come nostro database attivo e creiamo una tabella “utente” con tutte le colonne (id, nome utente, e così via), permettendo ancora una volta un comodo reset se necessario. Puoi eseguire questa query nel tuo client di database, se ne stai usando uno.

Se stai usando wsl, nella directory db puoi eseguire:

mysql -u -p < create-user-db.sql
Entra in modalità a schermo intero Esci dalla modalità a schermo intero

Configura e collegati al database MySQL:

Creare un file aggiuntivo nella directory db chiama db-connection.js, e copiare-incollare questo:

In questo file, prima importiamo il modulo dotenv e lo usiamo per leggere le informazioni di configurazione del database come db host, db user dal file.env.

Controlliamo la connessione in caso ci sia un problema con il database e poi rilasciamo la connessione.

Abbiamo un metodo query che restituisce una promessa del risultato della query.

Utilizziamo un blocco try-catch per catturare gli errori comuni di MySQL e restituire i codici di stato HTTP e i messaggi appropriati.

Alla fine del file, creiamo un’istanza della classe DBConnection e usiamo il metodo query, e nel model.js (che vedremo nel prossimo passo), useremo ancora il metodo query.

Creiamo il gestore degli errori:

Poi, creeremo il nostro gestore di errori.

Per farlo, per prima cosa, creeremo il file HttpException.utils.js sotto la directory utils, e faremo un copia-incolla di quanto segue:

La classe HttpException eredita la classe Error.
Il costruttore riceverà lo stato, il messaggio e i dati. Passeremo la variabile message al costruttore genitore usando super(message), e poi inizializzeremo le variabili di istanza status, message e data.

Dopo di che, creeremo un gestore di errori middleware nella directory middleware.
Creeremo un file error. middleware.js e copiamo-incolliamo quanto segue:

Possiamo vedere in fondo al file come sarà l’oggetto.

Il middleware otterrà req, res, e next callback, ma otterrà anche un argomento aggiuntivo, error (usando next(error) prima di arrivare a questo middleware).

Utilizziamo la destrutturazione per ottenere le variabili dall’oggetto error e impostiamo lo stato a 500 se non è stato configurato prima.

Dopo questo, se lo stato è 500, ci assicureremo di cambiare il messaggio in modo che l’utente riceva un generico messaggio di errore interno del server senza rivelare l’esatta natura del fallimento.

Dopo di che, creiamo un oggetto error con le proprietà type, status e message (data è opzionale).

Creiamo i file utils (helpers):

Nella directory utils, creiamo altri due file, common.utils.js, e userRoles.utils.js.

common.utils.js:

Questa funzione aiuta a impostare più campi per query preparate con coppie chiave-valore.
ColumnSet l’array di coppie chiave =?,
I valori dovrebbero quindi essere nello stesso ordine dell’array columnSet.

userRoles.utils.js:

module.exports = { Admin: 'Admin', SuperUser: 'SuperUser'}
Entrare in modalità schermo intero Uscire in modalità schermo intero

Creare funzione Async:

Crea un altro file chiamato awaitHandlerFactory.middleware.js nella directory del middleware e copia-incolla questo:

In generale, sappiamo che il middleware è solo un metodo asincrono che ottiene gli argomenti req, res e next, quindi, se vogliamo che questo middleware ottenga un argomento aggiuntivo, lo faremo in questo modo (lo useremo anche nel middleware auth nel prossimo passo).

Questa funzione otterrà un callback, eseguirà lo script del middleware e tenterà di innescare questo callback nel blocco try.
Se qualcosa va storto qui, catturerà l’errore e useremo il next(err) (che lo trasferirà al prossimo middleware => error.middleware.js).

Crea il middleware di autenticazione:

Un altro middleware di cui abbiamo bisogno è il middleware auth che useremo per controllare i permessi degli utenti tramite il modulo JWT.

Simile al middleware awaitHandlerFactory.middleware.js, qui abbiamo un middleware che richiede un argomento aggiuntivo (che è opzionale) => ruoli.

Ho usato try-catch per regolare lo stato di errore nell’area di cattura a 401 (se il token è scaduto, per esempio).

Al principio, stiamo cercando req.headers.authorization – se non è definito nell’intestazione o se l’intestazione non inizia con “Bearer “, l’utente riceverà una risposta 401. Se inizia con “Bearer “, otterremo il token e useremo la chiave segreta dal file.env per decifrarlo.

Verificheremo il token usando la funzione sincrona jwt.verify, che ottiene il token e la secretKey come argomenti e restituisce il payload decodificato, se la firma è valida e i campi opzionali expiration, audience o issuer sono validi. Altrimenti lancerà un errore.

Ora, possiamo trovare l’utente con questo token cercando l’id utente.
Se l’utente non esiste più, otterrà un’eccezione di 401 senza alcuna informazione.
Se l’utente esiste, controlleremo se l’utente attuale è il proprietario che cerca i suoi percorsi o se l’utente ha il ruolo per accedere a questo percorso.
Salviamo l’utente attuale nel caso in cui voglia ottenere i suoi dati sul prossimo middleware (come il percorso “whoami”).

Validazione dei dati utilizzando il modulo Express Validator:

Nella directory del middleware, creeremo un ulteriore file che useremo per verificare le proprietà del req.body.

Creare una sottocartella nella directory del middleware chiamata validators e creare un file in questa directory, userValidator.middleware.js. Copia-incolla questo:

In questo file, ho usato il modulo express-validator, che è molto facile da usare ogni volta che abbiamo bisogno di controllare alcune proprietà, controllare se la proprietà esiste, o creare controlli personalizzati con un messaggio personalizzato per l’utente se qualsiasi valore di proprietà non è valido.

Ora possiamo iniziare a creare i nostri file route, controller e model.

Definire le Routes:

Crea il file user.route.js nella directory routes e copia-incolla questo:

L’esempio precedente mostra come definire le rotte. Proviamo a scomporlo in pezzi:

  • Puoi creare un router usando express.Router().Ogni route può caricare una funzione middleware che gestisce la logica di business.UserController, per esempio porta tutti i middleware principali.Per usare il router, il router deve essere esportato come modulo e usato nell’app principale usando app.use(router_module).
  • Abbiamo usato il middleware auth per l’autenticazione e l’autorizzazione degli utenti, per controllare il token utente o il ruolo dell’utente per la rotta.Nel nostro esempio, alcune delle rotte usano il middleware auth per controllare l’autenticazione e l’autorizzazione dell’utente.Questo middleware sarà attivato prima del middleware principale (quello che contiene la logica di business).Il prossimo callback deve essere chiamato per passare il controllo al prossimo metodo middleware.Altrimenti, la richiesta sarà lasciata in sospeso.
  • awaitHandlerFactory (try-catch middleware) è usato per avvolgere tutti i middleware asincroni. In questo modo, se uno dei middleware lancia un errore, awaitHandlerFactory catturerà quell’errore.Potete vedere che tutte le nostre funzioni middleware sono avvolte dal middleware awaitHandlerFactory, che ci aiuta a gestire i nostri errori usando try-catch in un solo posto.
  • Inoltre, abbiamo lo schema createUserSchema, updateUserSchema e validateLogin per validare il corpo prima di iniziare il prossimo middleware.

La sintassi del metodo HTTP è:

Creare il Controller:

Creare il file user.controller.js nella directory dei controller e copiare-incollare questo:

Come detto sopra, il file controller contiene la nostra logica di business per gestire i nostri percorsi.
Nel nostro esempio, alcuni metodi usano la classe UserModel per interrogare il database per ottenere i dati.
Per restituire i dati in ogni middleware, usiamo res.send(result) per inviare una risposta al client.

Creare il modello:

E creare il file user.model.js nella directory models e copiare-incollare questo:

Questa classe crea la connessione tra il controller e il database.
Qui abbiamo tutti i metodi che ottengono gli argomenti dal controller, fanno una query, preparano le dichiarazioni, si connettono al database usando il metodo query dalla classe db-connection, inviano la richiesta con l’array di dichiarazioni preparate e ottengono il risultato indietro.
Ogni funzione restituisce il risultato al controller.

.gitIgnore:

Nel caso in cui decidiate di aggiungere questo progetto al vostro GitHub, non dimenticate di creare un file .gitignore e di fare un copia-incolla di questo:

node_modules.env
Entrare in modalità schermo intero Uscire in modalità schermo intero

Questo file dice semplicemente a git quali file deve ignorare.
Si dovrebbe evitare la directory node_modules perché è pesante e non necessaria per il repository.
Quando qualcuno clona questo repository, userà il comando “npm I” per installare tutte le dipendenze.
Ignorare il file .env serve a nascondere le tue configurazioni private agli altri sviluppatori che usano il tuo codice.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.