Configurare le variabili d'ambiente e MongoDB con Node.js

LT
Luca Terribili
Autore

Nei tutorial precedenti ho creato un backend con Node.js, Express e TypeScript. Ho separato le rotte dalla configurazione dell'applicazione e dal punto di ingresso del server, ottenendo una struttura più semplice da mantenere. Adesso posso iniziare a preparare il progetto per collegarsi a un database.

Prima di integrare MongoDB, però, devo affrontare un aspetto importante: la configurazione dell'applicazione. La porta del server, l'indirizzo del database e le credenziali non dovrebbero essere disseminati nei file TypeScript. Tenerli separati dal codice permette di modificare l'ambiente di esecuzione senza intervenire sull'applicazione e riduce il rischio di pubblicare accidentalmente informazioni riservate.

In questo tutorial utilizzerò le variabili d'ambiente per gestire la configurazione e MongoDB Atlas per ospitare il database. Per comunicare con MongoDB userò il driver ufficiale mongodb, che permette di eseguire operazioni sul database senza introdurre un ulteriore livello di astrazione.

L'obiettivo sarà predisporre una connessione riutilizzabile e verificare che il backend riesca a raggiungere il database. Non realizzerò ancora tutte le operazioni CRUD: prima voglio una connessione affidabile e una configurazione che possa essere riutilizzata nei prossimi tutorial.

Installare le dipendenze necessarie

Entro nella directory del progetto:

cd ~/dev/node-study

Per leggere le variabili d'ambiente da un file .env utilizzerò il pacchetto dotenv. Installo inoltre il driver ufficiale di MongoDB:

npm install dotenv mongodb

Il comando installa entrambi i pacchetti come dipendenze di produzione, perché saranno utilizzati dall'applicazione quando verrà eseguita.

dotenv legge le variabili definite nel file .env e le rende disponibili attraverso process.env. Il pacchetto mongodb, invece, contiene il driver con cui Node.js comunica con un server MongoDB.

In questo progetto utilizzo il driver ufficiale per lavorare direttamente con database e collezioni. In seguito potrò valutare una libreria come Mongoose, che aggiunge un sistema di schemi e modelli, ma per ora preferisco capire come funziona il driver sottostante.

Creare il database su MongoDB Atlas

Per utilizzare MongoDB senza installare un server locale, posso creare un cluster su MongoDB Atlas. Dopo aver effettuato l'accesso al servizio, creo un progetto e un cluster seguendo le opzioni disponibili nel pannello.

Per collegarmi al cluster dovrò configurare un utente del database e autorizzare l'indirizzo IP dal quale effettuerò la connessione. Questi sono due controlli distinti: l'utente stabilisce con quali credenziali posso accedere, mentre la configurazione di rete determina da quali indirizzi è consentito raggiungere il cluster.

Dalla sezione dedicata alla connessione recupero la stringa di connessione per il driver. Sarà simile a questa:

mongodb+srv://<utente>:<password>@<cluster-url>/

I valori tra parentesi angolari sono segnaposto e devono essere sostituiti con quelli reali forniti dalla configurazione del cluster. Non devo copiare letteralmente la stringa d'esempio, né pubblicare quella contenente le mie credenziali.

La stringa di connessione è un'informazione sensibile: se contiene una password, chiunque la ottenga potrebbe tentare di utilizzarla. Per questo motivo la conserverò nel file .env, che escluderò dal controllo versione.

Configurare le variabili d'ambiente

Nella directory principale del progetto creo il file .env:

touch .env

Al suo interno inserisco la configurazione:

PORT=3000
MONGODB_URI=mongodb+srv://<utente>:<password>@<cluster-url>/
MONGODB_DB=node_study

Devo sostituire il valore di MONGODB_URI con la stringa effettiva fornita da Atlas, rimuovendo i segnaposto e utilizzando le credenziali configurate nel servizio. Se la password contiene caratteri speciali, potrebbe essere necessario codificarli correttamente nella stringa URI.

La variabile PORT indica la porta sulla quale Express si metterà in ascolto. MONGODB_URI contiene l'indirizzo necessario per raggiungere il cluster, mentre MONGODB_DB identifica il database che utilizzerò nell'applicazione.

MongoDB può creare il database quando viene scritto il primo dato, quindi non è indispensabile che esista già un database con quel nome. L'importante è che l'utente configurato abbia i permessi necessari.

Proteggere il file .env

Il file .env non deve essere pubblicato su Git. Aggiungo quindi queste righe al file .gitignore, creandolo se necessario:

.env
.env.*
!.env.example
dist/
node_modules/

Se il file .gitignore contiene già altre regole, le aggiungo senza cancellare quelle esistenti.

Escludere .env impedisce che venga aggiunto accidentalmente al repository in condizioni normali, ma non rimuove un file che sia già stato tracciato da Git. Se le credenziali sono già state pubblicate, devo revocarle o cambiarle: cancellare il file da un commit successivo non elimina necessariamente le copie presenti nella cronologia.

Per documentare la configurazione senza distribuire segreti, posso creare un file .env.example con valori segnaposto:

PORT=3000
MONGODB_URI=
MONGODB_DB=node_study

Questo file può essere versionato e mostra quali variabili sono necessarie, senza contenere credenziali reali.

Caricare le variabili all'avvio

Ora devo fare in modo che Node.js carichi il file .env prima di utilizzare le variabili. Nel nostro progetto, che utilizza ES Modules, posso importare dotenv/config nel punto di ingresso.

Modifico src/index.ts in questo modo:

import 'dotenv/config';
import app from './app.js';

const port = Number(process.env.PORT) || 3000;

app.listen(port, () => {
  console.log(`Server in ascolto sulla porta ${port}`);
});

L'import dotenv/config carica il file .env all'avvio del programma. Le variabili diventano quindi accessibili attraverso process.env.

La porta viene convertita in numero con Number(), perché le variabili d'ambiente sono rappresentate come stringhe. Se PORT non è impostata o non produce un valore numerico valido, l'espressione utilizza 3000 come valore predefinito. Per una configurazione di produzione più rigorosa sarà opportuno validare esplicitamente le variabili e segnalare i valori non validi, anziché affidarsi soltanto a un fallback.

A questo punto posso avviare nuovamente il server:

npx tsx src/index.ts

Il server dovrebbe continuare a rispondere sulla porta 3000. La differenza è che la porta non è più fissata direttamente nel codice.

Creare il modulo per MongoDB

Voglio che la gestione della connessione al database sia separata dal resto dell'applicazione. Creo quindi una directory dedicata:

mkdir -p src/config

Al suo interno creo src/config/database.ts:

import { MongoClient, type Db } from 'mongodb';

const uri = process.env.MONGODB_URI;
const dbName = process.env.MONGODB_DB;

if (!uri) {
  throw new Error('La variabile MONGODB_URI non è configurata');
}

if (!dbName) {
  throw new Error('La variabile MONGODB_DB non è configurata');
}

const client = new MongoClient(uri);

let database: Db | undefined;

export async function connectToDatabase(): Promise<Db> {
  if (database) {
    return database;
  }

  await client.connect();

  database = client.db(dbName);

  console.log(`Connesso al database ${dbName}`);

  return database;
}

Il modulo importa MongoClient, che gestisce la connessione al server MongoDB, e il tipo Db, che rappresenta un database.

Prima di creare il client controllo che le due variabili richieste siano presenti. Se manca l'URI o il nome del database, il programma genera un errore esplicito invece di tentare una connessione configurata in modo incompleto.

La chiamata new MongoClient(uri) crea il client. La connessione vera e propria viene stabilita quando eseguo client.connect(), che è un'operazione asincrona e per questo richiede await.

La variabile database conserva il riferimento al database dopo la prima connessione. Se richiamo connectToDatabase() una seconda volta, la funzione restituisce il riferimento già disponibile invece di aprire nuovamente la connessione.

La funzione restituisce una Promise perché il collegamento al server richiede un'operazione asincrona. Il tipo Db consente a TypeScript di conoscere i metodi disponibili per interagire con il database.

Questo modulo prepara la connessione, ma da solo non la avvia: dovrò richiamare connectToDatabase() nel punto di ingresso dell'applicazione.

Collegare il database all'avvio del server

Adesso modifico src/index.ts per stabilire la connessione a MongoDB prima di avviare Express:

import 'dotenv/config';
import app from './app.js';
import { connectToDatabase } from './config/database.js';

const port = Number(process.env.PORT) || 3000;

async function startServer(): Promise<void> {
  await connectToDatabase();

  app.listen(port, () => {
    console.log(`Server in ascolto sulla porta ${port}`);
  });
}

startServer().catch((error: unknown) => {
  console.error('Errore durante l’avvio del server:', error);
  process.exitCode = 1;
});

Ho racchiuso la procedura di avvio in una funzione asincrona. In questo modo posso utilizzare await per attendere che MongoDB sia raggiungibile prima di aprire la porta HTTP.

Se la connessione riesce, il server Express si avvia normalmente. Se invece la connessione fallisce, l'errore viene intercettato dal blocco catch e il processo viene contrassegnato con un codice di uscita diverso da zero.

La scelta di attendere la connessione evita di avviare un backend che sembra funzionare ma non può ancora accedere al database. Nelle applicazioni reali bisognerà inoltre gestire la chiusura ordinata del server e del client MongoDB, ma introdurrò questa parte quando affronterò il ciclo di vita dell'applicazione.

Verificare la connessione

Prima di avviare il programma, controllo che il file .env contenga i valori corretti e che Atlas consenta la connessione dal mio indirizzo IP.

Eseguo il controllo statico di TypeScript:

npx tsc --noEmit

Se non vengono segnalati errori, avvio il backend:

npx tsx src/index.ts

Se la configurazione è corretta, dovrei vedere messaggi simili a questi:

Connesso al database node_study
Server in ascolto sulla porta 3000

Il nome del database visualizzato dipende dal valore che ho inserito in MONGODB_DB.

Se la connessione fallisce, controllo innanzitutto che l'URI sia corretto, che le credenziali siano valide e che l'indirizzo IP sia autorizzato nella configurazione di rete di Atlas. Verifico inoltre che la password sia stata codificata correttamente se contiene caratteri riservati nelle URI.

Se il server si avvia correttamente, posso provare anche la rotta già creata:

curl -i http://localhost:3000/

La risposta dovrebbe essere ancora quella del tutorial precedente. Per ora abbiamo aggiunto la connessione al database senza modificare il comportamento dell'endpoint HTTP.

Conclusione

In questo tutorial ho introdotto le variabili d'ambiente, installato dotenv e il driver ufficiale MongoDB, preparato la configurazione per MongoDB Atlas e creato un modulo dedicato alla connessione.

Ho anche modificato il punto di ingresso del backend affinché la connessione al database venga stabilita prima dell'avvio del server Express. Questo approccio separa la configurazione dal codice e impedisce che il server venga considerato pronto quando il database non è ancora raggiungibile.

Nel prossimo tutorial potrò utilizzare la connessione per eseguire le prime operazioni sui dati. Inizierò con una collezione semplice e introdurrò le operazioni CRUD, cioè creazione, lettura, aggiornamento ed eliminazione dei documenti, mantenendo la struttura modulare che abbiamo costruito finora.