← Back to course

PostgreSQL e applicazioni: collegare il database a progetti reali

PostgreSQL e applicazioni: collegare il database a progetti reali

Bentornato.

Nella lezione precedente hai costruito un piccolo database per un negozio.

Hai creato:

customers
categories
products
orders
order_items

Hai usato:

Molto bene.

Ora PostgreSQL non è più solo una creatura misteriosa del terminale.

Sta diventando uno strumento reale.

Ma c’è ancora una domanda molto importante:

Come fa un’applicazione a collegarsi davvero a PostgreSQL?

Perché nei progetti reali gli utenti non aprono psql e scrivono SQL a mano.

Di solito.

Un’applicazione backend si collega al database.

Poi l’applicazione legge dati.

Scrive dati.

Aggiorna dati.

Elimina dati.

Speriamo non tutti i dati.

Quello sarebbe un martedì molto drammatico.

Oggi impareremo come le applicazioni si collegano a PostgreSQL.

Questa lezione non riguarda un framework specifico.

Le idee valgono per:

Django
Spring Boot
Node.js
Express
NestJS
Laravel
Rails
applicazioni Go
qualsiasi backend che usa PostgreSQL

I framework cambiano.

I principi di connessione restano.

Molto utile.

Molto database.

Molto “per favore non mettere la password direttamente nel codice”.

Cosa imparerai

In questa lezione imparerai:

Alla fine di questa lezione capirai il ponte tra PostgreSQL e le applicazioni backend.

Perché un database senza applicazione è utile.

Ma un database collegato a un’applicazione diventa potente.

Come un magazzino collegato a un negozio.

O come una macchina del caffè collegata a uno sviluppatore stanco.

Infrastruttura importante.

L’idea di base

Un’applicazione ha bisogno di informazioni di connessione per parlare con PostgreSQL.

Di solito servono:

host
porta
nome del database
username
password

Esempio:

host: localhost
porta: 5432
database: shop_db
username: shop_user
password: strong_password

L’applicazione usa questi valori per aprire una connessione.

Poi può inviare query SQL.

Per esempio:

SELECT * FROM products;

L’applicazione invia la query.

PostgreSQL la esegue.

PostgreSQL restituisce il risultato.

L’applicazione usa il risultato.

Magari mostra i prodotti su un sito.

Magari crea una risposta API.

Magari genera un report.

Magari si rompe perché qualcuno ha dimenticato un punto e virgola.

Classico.

Host di PostgreSQL

L’host dice all’applicazione dove sta girando PostgreSQL.

Per lo sviluppo locale, l’host è spesso:

localhost

oppure:

127.0.0.1

Questo significa:

PostgreSQL sta girando sulla stessa macchina dell’applicazione.

Esempio:

host=localhost

In produzione, PostgreSQL può girare su un altro server.

Esempio:

host=db.example.com

oppure:

host=10.0.0.5

oppure dentro Docker:

host=postgres

L’host dipende da dove vive il database.

L’applicazione deve avere l’indirizzo corretto.

Altrimenti busserà alla porta sbagliata.

PostgreSQL non risponde da una casa dove non abita.

Molto ragionevole.

Porta di PostgreSQL

PostgreSQL di solito ascolta sulla porta:

5432

Quindi una connessione comune usa:

port=5432

Una porta è come il numero di una porta su un server.

Il server può eseguire molti servizi.

PostgreSQL usa di default la porta 5432.

Se hai cambiato la porta, devi usare quella nuova.

Ma per principianti, la maggior parte delle volte:

5432

è la risposta.

Un raro momento di semplicità.

Goditelo.

Nome del database

Un server PostgreSQL può contenere molti database.

Per esempio:

learning_postgresql
shop_db
portfolio_db
blog_db

La tua applicazione deve sapere quale database usare.

Esempio:

database=shop_db

Se l’applicazione si collega al database sbagliato, succedono cose strane.

Le tabelle possono mancare.

I dati possono sembrare vecchi.

Il tuo cervello può iniziare a fare debug dell’universo sbagliato.

Controlla sempre il nome del database.

Piccolo errore.

Grande mal di testa.

Username e password

PostgreSQL usa utenti e password per controllare l’accesso.

Esempio:

username=shop_user
password=strong_password

L’utente determina cosa può fare l’applicazione.

Un utente può avere permessi come:

collegarsi a un database
leggere tabelle
inserire righe
aggiornare righe
eliminare righe
creare tabelle

Non dare a ogni applicazione pieni poteri da superuser.

È come dare una motosega a un bambino perché voleva tagliare un foglio.

Tecnicamente efficace.

Profondamente poco saggio.

Non usare l’utente postgres nelle applicazioni

PostgreSQL spesso ha un utente amministrativo predefinito chiamato:

postgres

Questo utente è potente.

Molto potente.

Troppo potente per applicazioni normali.

Non usare postgres come utente della tua applicazione.

Crea invece un utente specifico per l’applicazione.

Esempio:

shop_user
blog_user
app_user
portfolio_user

Perché?

Perché se la tua applicazione ha un bug o viene attaccata, vuoi limitare i danni.

Un utente applicativo dovrebbe avere solo i permessi necessari.

Non le chiavi dell’intero regno.

Il regno del database ha draghi.

E fatture.

Creare un database per un’applicazione

Apri PostgreSQL come utente postgres:

sudo -iu postgres psql

Crea un database:

CREATE DATABASE shop_db;

Crea un utente:

CREATE USER shop_user WITH PASSWORD 'change_this_password';

Dai all’utente accesso al database:

GRANT CONNECT ON DATABASE shop_db TO shop_user;

Ora collegati al database:

\c shop_db

Dai permessi sullo schema public:

GRANT USAGE ON SCHEMA public TO shop_user;

Permetti all’utente di lavorare con le tabelle:

GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO shop_user;

Permetti all’utente di usare le sequenze.

Questo è importante per gli ID SERIAL.

GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO shop_user;

Per le tabelle future, puoi impostare privilegi predefiniti:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO shop_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO shop_user;

Questo è già molto meglio che usare il superuser postgres.

L’applicazione ottiene accesso.

Ma non potere illimitato.

Molto civile.

Testare il nuovo utente

Esci da PostgreSQL:

\q

Ora prova a collegarti con il nuovo utente:

psql -h localhost -U shop_user -d shop_db

PostgreSQL chiederà la password.

Inserisci la password che hai creato.

Se la connessione funziona, entrerai in psql.

Poi testa:

SELECT current_user;

Dovresti vedere:

shop_user

Bene.

Ora anche la tua applicazione può usare questo utente.

La porta del database si apre.

Ma solo con la chiave giusta.

Molto sicuro.

Molto adulto.

Cos’è una connection string?

Una connection string mette tutte le informazioni di connessione in una riga.

Formato comune PostgreSQL:

postgresql://username:password@host:port/database

Esempio:

postgresql://shop_user:change_this_password@localhost:5432/shop_db

Contiene:

username: shop_user
password: change_this_password
host: localhost
porta: 5432
database: shop_db

Molti strumenti e framework usano connection string.

Sono compatte.

Utili.

E pericolose se le incolli ovunque.

Perché contengono password.

Tratta le connection string come chiavi.

Non pubblicarle.

Non committarle su GitHub.

Non inviarle a persone casuali.

Non metterle negli screenshot.

Il tuo futuro io ti ringrazierà.

Anche le persone della sicurezza smetteranno di urlare.

Variabili d’ambiente

Le applicazioni di solito leggono le impostazioni del database dalle variabili d’ambiente.

Le variabili d’ambiente sono valori salvati fuori dal codice.

Esempio:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password

Oppure:

DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db

Perché usare variabili d’ambiente?

Perché il codice non dovrebbe contenere segreti.

Male:

password direttamente nel codice sorgente

Bene:

password in una variabile d’ambiente

Questo ti permette di usare impostazioni diverse per:

sviluppo locale
testing
produzione

Stesso codice.

Configurazione diversa.

Molto utile.

Molto professionale.

Molto “ho imparato da errori dolorosi”.

Il file .env

Durante lo sviluppo locale, molti progetti usano un file .env.

Esempio:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password

Oppure:

DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db

L’applicazione legge questo file quando parte.

Importante:

Non committare file .env con password reali.

Aggiungi .env a .gitignore.

Esempio .gitignore:

.env
.env.local
.env.production

Puoi creare un file di esempio sicuro:

.env.example

Esempio:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=your_password_here

Questo mostra agli altri sviluppatori quali variabili servono.

Ma non espone segreti reali.

Buona pratica.

Molto buona.

Stella d’oro.

Stella d’oro da database.

Esempio: impostazioni generiche di un’applicazione

Molte applicazioni hanno bisogno di impostazioni come queste:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password

Poi l’applicazione costruisce una connessione.

Concettualmente:

Collegati a DB_HOST sulla porta DB_PORT.
Usa DB_NAME.
Accedi con DB_USER e DB_PASSWORD.

Alcuni framework preferiscono una sola URL:

DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db

Entrambi gli stili sono comuni.

Quale usare dipende dal framework.

Ma l’idea è la stessa.

La tua app ha bisogno delle indicazioni per trovare il database.

Senza indicazioni, vaga come un turista perso con il GPS rotto.

Esempio: impostazioni Django

In Django, le impostazioni del database spesso assomigliano a queste:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "shop_db",
        "USER": "shop_user",
        "PASSWORD": "change_this_password",
        "HOST": "localhost",
        "PORT": "5432",
    }
}

Ma mettere la password direttamente qui non è una buona idea.

Meglio così:

import os

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("DB_NAME"),
        "USER": os.environ.get("DB_USER"),
        "PASSWORD": os.environ.get("DB_PASSWORD"),
        "HOST": os.environ.get("DB_HOST", "localhost"),
        "PORT": os.environ.get("DB_PORT", "5432"),
    }
}

Ora la password arriva dalle variabili d’ambiente.

Molto meglio.

Django riceve i dettagli della connessione.

Il tuo codice resta più sicuro.

Tutti sono meno nervosi.

Tranne forse lo sviluppatore CSS.

Ma quello è un altro corso.

Esempio: impostazioni Spring Boot

In Spring Boot, application.properties può essere così:

spring.datasource.url=jdbc:postgresql://localhost:5432/shop_db
spring.datasource.username=shop_user
spring.datasource.password=change_this_password

Meglio con variabili d’ambiente:

spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}

Poi il tuo ambiente può contenere:

DB_URL=jdbc:postgresql://localhost:5432/shop_db
DB_USER=shop_user
DB_PASSWORD=change_this_password

Questo è molto più sicuro che scrivere segreti nel codice.

Nei progetti reali, la password del database non dovrebbe vivere nel repository Git.

Git ricorda tutto.

Come un elefante.

Ma con la cronologia dei commit.

Esempio: connessione Node.js

Un’applicazione Node.js può usare una connection string:

DATABASE_URL=postgresql://shop_user:change_this_password@localhost:5432/shop_db

Poi l’app la legge.

Concettualmente:

const connectionString = process.env.DATABASE_URL;

Molte librerie Node.js supportano questo stile.

L’idea importante non è la libreria esatta.

L’idea importante è:

Leggi i segreti dalle variabili d’ambiente.
Non scriverli direttamente nel codice.

Questa regola ti segue in tutti i linguaggi.

Python.

Java.

JavaScript.

Go.

PHP.

Ovunque.

I segreti nel codice sono come finestre aperte in inverno.

Pessima idea.

Costosa più tardi.

Sviluppo locale vs produzione

Sviluppo locale di solito significa:

applicazione e database girano sul tuo computer
host = localhost
porta = 5432

Produzione di solito significa:

l’applicazione gira su un server
il database può girare su un altro server
host non è localhost
la password è più forte
la sicurezza conta di più
i backup contano di più
il monitoraggio conta di più

In sviluppo locale, gli errori sono fastidiosi.

In produzione, gli errori diventano fatture.

Molto motivante.

Per questo la configurazione deve essere flessibile.

Puoi avere:

database locale
database di test
database di produzione

Ogni ambiente ha valori diversi.

Stesso codice applicativo.

Variabili d’ambiente diverse.

Questo è normale.

Questo è professionale.

Questo evita di modificare il codice solo per collegarsi da un’altra parte.

Docker e nomi host PostgreSQL

Se la tua applicazione e PostgreSQL girano dentro Docker Compose, l’host spesso è il nome del servizio.

Esempio:

services:
  app:
    environment:
      DB_HOST: postgres
      DB_PORT: 5432

  postgres:
    image: postgres

Dentro Docker, l’app può collegarsi a:

postgres

non a:

localhost

Perché?

Perché dentro il container dell’app, localhost significa il container dell’app stesso.

Non il container PostgreSQL.

Questo è un errore molto comune.

Molto comune.

Molto doloroso.

Molto Docker.

Regola semplice:

Dal tuo computer: localhost può funzionare.
Da un altro container: usa il nome del servizio.

Quindi in Docker Compose l’host del database potrebbe essere:

postgres

oppure:

db

dipende dal nome del servizio.

Il networking Docker è potente.

E ogni tanto piccante.

Errori comuni di connessione

Connection refused

Errore esempio:

connection refused

Di solito significa:

PostgreSQL non è in esecuzione.
Host sbagliato.
Porta sbagliata.
PostgreSQL non ascolta lì.
Firewall blocca la connessione.

Controlla lo stato di PostgreSQL:

systemctl status postgresql

Su alcuni sistemi, il nome del servizio può essere diverso.

Puoi anche provare:

psql -h localhost -U shop_user -d shop_db

Se psql non riesce a collegarsi, probabilmente nemmeno l’applicazione ci riuscirà.

Non dare subito la colpa al framework.

Prima controlla il database.

I framework sono spesso colpevoli.

Ma non sempre.

Password authentication failed

Errore esempio:

password authentication failed for user

Di solito significa:

username sbagliato
password sbagliata
utente inesistente
variabile d’ambiente con valore sbagliato

Controlla lo username:

SELECT current_user;

Controlla che l’utente esista:

\du

Se serve, reimposta la password:

ALTER USER shop_user WITH PASSWORD 'new_password_here';

Poi aggiorna la variabile d’ambiente.

Non aggiornare solo la tua memoria.

Le applicazioni non possono leggere la tua memoria.

Per fortuna.

Database does not exist

Errore esempio:

database "shop_db" does not exist

Di solito significa esattamente quello che dice.

Controlla i database:

\l

Crea il database se serve:

CREATE DATABASE shop_db;

Oppure correggi il nome del database nella configurazione.

Questo errore spesso è solo un typo.

Un piccolo typo con grande personalità.

Permission denied

Errore esempio:

permission denied for table products

Questo significa che l’utente si è collegato correttamente.

Ma non ha il permesso di fare qualcosa.

Dai i permessi:

GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO shop_user;

Per le sequenze:

GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO shop_user;

Se l’app può leggere ma non può inserire righe con ID SERIAL, il problema può essere nei permessi delle sequenze.

I permessi PostgreSQL sono potenti.

E anche leggermente fastidiosi.

Come una guardia di sicurezza seria con documenti da compilare.

Table does not exist

Errore esempio:

relation "products" does not exist

Possibili motivi:

Ti sei collegato al database sbagliato.
La tabella non è stata creata.
La tabella è in un altro schema.
La migration non è stata eseguita.
Il nome della tabella è diverso.

Controlla le tabelle:

\dt

Controlla il database corrente:

SELECT current_database();

Questo errore succede spesso quando l’applicazione si collega a un database, ma tu hai creato le tabelle in un altro.

Classico.

Doloroso.

Molto educativo.

Regole base di sicurezza

Ecco alcune regole semplici.

Non sicurezza avanzata.

Sopravvivenza base.

Non scrivere password nel codice

Male:

password dentro il codice sorgente

Bene:

password in variabile d’ambiente

Il codice può essere condiviso.

I segreti no.

Molto semplice.

Molto ignorato.

Molto pericoloso.

Non committare .env

Aggiungi .env a .gitignore.

.env
.env.local
.env.production

Se per errore committi una password reale, cambiala.

Non eliminare solo il file dall’ultimo commit facendo finta che nulla sia successo.

La cronologia Git ricorda.

Git non è tuo amico in questa situazione.

Git è un testimone.

Usa un utente database separato

Non usare il superuser postgres nell’applicazione.

Crea un utente specifico:

shop_user

Dagli solo i permessi necessari.

Questo limita i danni se qualcosa va storto.

E qualcosa prima o poi va storto.

Non è pessimismo.

È sviluppo software.

Usa password forti

Non usare:

password
123456
postgres
admin
qwerty

Queste non sono password.

Sono inviti.

Usa password forti.

Soprattutto in produzione.

Il tuo database merita di meglio.

Tieni privato il database di produzione

Un database PostgreSQL di produzione di solito non dovrebbe essere aperto a tutto Internet.

Meglio:

rete privata
regole firewall
accesso limitato per IP
configurazione hosting sicura

Se il database è raggiungibile pubblicamente, la sicurezza diventa molto più seria.

Ai database piace la privacy.

Anche agli utenti.

Anche agli avvocati.

Migration delle applicazioni

Molti framework usano migration.

Una migration è una modifica controllata del database.

Esempi:

creare una tabella
aggiungere una colonna
cambiare tipo di colonna
creare un indice
eliminare una tabella

Invece di modificare manualmente il database ogni volta, il framework tiene traccia delle modifiche.

Django ha le migration.

Spring Boot spesso usa strumenti come Flyway o Liquibase.

Anche gli ORM di Node.js hanno strumenti di migration.

L’idea è:

La struttura del database deve essere versionata.

Questo conta perché applicazione e database devono corrispondere.

Se il codice si aspetta una colonna chiamata email, ma il database non ce l’ha, l’app si rompe.

Con grande sicurezza.

Le migration aiutano a tenere la struttura sotto controllo.

Non sono magia.

Ma sono meglio delle modifiche manuali casuali a mezzanotte.

ORM vs SQL puro

Molte applicazioni usano un ORM.

ORM significa Object-Relational Mapping.

Esempi:

Django ORM
Hibernate / JPA
Prisma
TypeORM
Sequelize
SQLAlchemy

Un ORM ti permette di lavorare con le righe del database come oggetti.

Idea esempio:

Oggetto Product
Oggetto Customer
Oggetto Order

L’ORM genera SQL.

È comodo.

Ma devi comunque capire SQL.

Perché?

Perché quando qualcosa diventa lento o si rompe, l’ORM non salverà la tua anima.

Genererà SQL.

PostgreSQL eseguirà SQL.

I problemi di prestazioni restano problemi SQL.

Quindi imparare SQL non è tempo perso.

Anche se usi un ORM.

Anzi, soprattutto se usi un ORM.

Perché ora sai cosa succede sotto la coperta.

E a volte sotto la coperta c’è una query mostruosa.

Flusso comune di un’applicazione

Un flusso backend tipico assomiglia a questo:

L’utente apre il sito.
Il frontend invia una richiesta al backend.
Il backend si collega a PostgreSQL.
Il backend esegue una query.
PostgreSQL restituisce dati.
Il backend restituisce JSON o HTML.
Il frontend mostra il risultato.

Esempio:

GET /products

Query backend:

SELECT
  p.id,
  p.name,
  p.price,
  c.name AS category_name
FROM products AS p
JOIN categories AS c
ON p.category_id = c.id
ORDER BY p.name;

Il backend restituisce JSON:

[
  {
    "id": 1,
    "name": "Laptop",
    "price": 900.00,
    "category": "Electronics"
  }
]

Così PostgreSQL diventa parte di un’applicazione.

L’utente non vede mai SQL.

Ma SQL fa il lavoro.

Come un tecnico dietro le quinte.

Silenzioso.

Importante.

Un po’ sottovalutato.

Connection pooling

Aprire una connessione al database richiede tempo.

Se ogni richiesta apre una nuova connessione e la chiude subito, le prestazioni possono peggiorare.

Molte applicazioni usano un connection pool.

Un connection pool mantiene aperte alcune connessioni al database e le riutilizza.

Idea semplice:

Non creare una nuova connessione ogni volta.
Riusa connessioni esistenti.

La maggior parte dei framework gestisce questa cosa per te.

Ma è bene conoscere l’idea.

Perché i sistemi in produzione tengono molto alle connessioni.

Troppe connessioni possono sovraccaricare PostgreSQL.

Troppo poche connessioni possono rallentare l’app.

L’equilibrio conta.

Come il caffè.

Troppo poco è male.

Troppo e puoi sentire i colori.

Pratica

Crea un nuovo database:

CREATE DATABASE app_demo_db;

Crea un nuovo utente:

CREATE USER app_demo_user WITH PASSWORD 'change_this_password';

Dai il permesso di connessione:

GRANT CONNECT ON DATABASE app_demo_db TO app_demo_user;

Collegati al database:

\c app_demo_db

Crea una tabella semplice:

CREATE TABLE messages (
  id SERIAL PRIMARY KEY,
  title VARCHAR(150) NOT NULL,
  content TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Dai i permessi sulle tabelle:

GRANT USAGE ON SCHEMA public TO app_demo_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_demo_user;

Dai i permessi sulle sequenze:

GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_demo_user;

Inserisci una riga di test:

INSERT INTO messages (title, content)
VALUES ('Hello App', 'This row could be read by a backend application.');

Testa la connessione:

psql -h localhost -U app_demo_user -d app_demo_db

Poi esegui:

SELECT * FROM messages;

Se funziona, hai un database, un utente, permessi e una connessione funzionante.

Questa è la base dell’integrazione con le applicazioni.

Non appariscente.

Molto importante.

Come un buon impianto elettrico.

Nessuno lo vede.

Tutti si lamentano quando non funziona.

Mini sfida

Crea un database per un’applicazione blog.

Requisiti:

nome database: blog_app_db
nome utente: blog_app_user
tabelle: authors, posts

Regole:

Crea il database:

CREATE DATABASE blog_app_db;

Crea l’utente:

CREATE USER blog_app_user WITH PASSWORD 'change_this_password';

Dai il permesso di connessione:

GRANT CONNECT ON DATABASE blog_app_db TO blog_app_user;

Collegati:

\c blog_app_db

Crea le tabelle:

CREATE TABLE authors (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(100) NOT NULL
);
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  author_id INTEGER NOT NULL REFERENCES authors(id),
  title VARCHAR(150) NOT NULL,
  content TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Dai i permessi:

GRANT USAGE ON SCHEMA public TO blog_app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO blog_app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO blog_app_user;

Crea indici utili:

CREATE INDEX idx_posts_author_id
ON posts(author_id);
CREATE INDEX idx_posts_created_at
ON posts(created_at);

Ora crea un file .env.example per l’applicazione:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=blog_app_db
DB_USER=blog_app_user
DB_PASSWORD=your_password_here

Oppure:

DATABASE_URL=postgresql://blog_app_user:your_password_here@localhost:5432/blog_app_db

Poi rispondi:

Quali valori sono sicuri da committare?
Quali valori devono restare segreti?
Perché .env deve essere ignorato da Git?
Perché l’app non deve usare l’utente postgres?

Questo è pratico.

Questo è reale.

Questa è la base noiosa che previene disastri emozionanti.

E nei database, noioso spesso è buono.

Molto buono.

Errori comuni

Usare postgres ovunque

Non farlo.

L’utente postgres serve per amministrazione.

La tua app dovrebbe avere il suo utente.

Dagli solo i permessi necessari.

Questa è sicurezza di base.

La sicurezza di base non è opzionale.

È come indossare scarpe in officina.

Scrivere segreti nel codice

Male:

spring.datasource.password=my_real_password

Male:

"PASSWORD": "my_real_password"

Male:

const password = "my_real_password";

Usa variabili d’ambiente.

Il tuo codice non è una cassaforte.

Dimenticare i permessi sulle sequenze

Se la tua app può inserire righe ma fallisce con errori di permessi sulle sequenze, ricorda:

GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;

SERIAL usa sequenze.

Le sequenze hanno bisogno di permessi.

PostgreSQL è preciso.

A volte dolorosamente preciso.

Collegarsi al database sbagliato

Controlla sempre:

SELECT current_database();

Molti bug sono semplicemente:

Ho creato la tabella in un database.
La mia app si collega a un altro database.

È doloroso.

Ma molto comune.

Usare localhost male in Docker

Dentro Docker Compose, localhost dal container dell’app di solito significa il container dell’app.

Non il container del database.

Usa il nome del servizio database.

Esempio:

postgres

oppure:

db

Docker non ha torto.

È solo molto letterale.

Come PostgreSQL.

Probabilmente vanno d’accordo.

Riassunto

Oggi hai imparato:

Questa è una lezione importante.

Ora capisci come PostgreSQL si collega alle applicazioni reali.

Qui la conoscenza del database diventa potere backend.

Il database conserva la verità.

L’applicazione usa la verità.

L’utente vede il risultato.

E da qualche parte nel mezzo, uno sviluppatore spera che le variabili d’ambiente siano corrette.

Molto reale.

Molto professionale.

Molto lunedì.

Prossima lezione

Nella prossima lezione parleremo di backup, restore e manutenzione base del database.

Perché creare un database è buono.

Usare un database è meglio.

Ma riuscire a recuperare i dati dopo un problema non ha prezzo.

I backup sono noiosi.

Fino al giorno in cui ti salvano la vita.

Allora diventano bellissimi.