← Back to course

PostgreSQL i aplikacje: łączenie bazy danych z prawdziwymi projektami

PostgreSQL i aplikacje: łączenie bazy danych z prawdziwymi projektami

Witaj z powrotem.

W poprzedniej lekcji zbudowałeś małą bazę danych dla sklepu.

Utworzyłeś:

customers
categories
products
orders
order_items

Użyłeś:

Bardzo dobrze.

Teraz PostgreSQL nie jest już tylko tajemniczym stworzeniem z terminala.

Zaczyna być prawdziwym narzędziem.

Ale zostaje jedno bardzo ważne pytanie:

Jak aplikacja naprawdę łączy się z PostgreSQL?

Bo w prawdziwych projektach użytkownicy nie otwierają psql i nie wpisują SQL ręcznie.

Zazwyczaj.

Aplikacja backendowa łączy się z bazą danych.

Potem aplikacja czyta dane.

Zapisuje dane.

Aktualizuje dane.

Usuwa dane.

Miejmy nadzieję, że nie wszystkie dane.

To byłby bardzo dramatyczny wtorek.

Dzisiaj nauczymy się, jak aplikacje łączą się z PostgreSQL.

Ta lekcja nie dotyczy jednego konkretnego frameworka.

Te zasady pasują do:

Django
Spring Boot
Node.js
Express
NestJS
Laravel
Rails
aplikacji Go
każdego backendu, który używa PostgreSQL

Frameworki się zmieniają.

Zasady połączenia zostają.

Bardzo przydatne.

Bardzo bazodanowe.

Bardzo “proszę, nie wkładaj hasła bezpośrednio do kodu”.

Czego się nauczysz

W tej lekcji nauczysz się:

Pod koniec tej lekcji zrozumiesz most między PostgreSQL a aplikacjami backendowymi.

Bo baza danych bez aplikacji jest użyteczna.

Ale baza danych połączona z aplikacją staje się potężna.

Jak magazyn połączony ze sklepem.

Albo jak ekspres do kawy połączony ze zmęczonym programistą.

Ważna infrastruktura.

Podstawowa idea

Aplikacja potrzebuje informacji połączeniowych, żeby rozmawiać z PostgreSQL.

Zwykle potrzebuje:

host
port
nazwa bazy danych
username
password

Przykład:

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

Aplikacja używa tych wartości, żeby otworzyć połączenie.

Potem może wysyłać zapytania SQL.

Na przykład:

SELECT * FROM products;

Aplikacja wysyła zapytanie.

PostgreSQL je wykonuje.

PostgreSQL zwraca wynik.

Aplikacja używa wyniku.

Może pokazuje produkty na stronie.

Może tworzy odpowiedź API.

Może generuje raport.

Może się psuje, bo ktoś zapomniał średnika.

Klasyka.

Host PostgreSQL

Host mówi aplikacji, gdzie działa PostgreSQL.

W lokalnym developmencie hostem często jest:

localhost

albo:

127.0.0.1

To oznacza:

PostgreSQL działa na tej samej maszynie co aplikacja.

Przykład:

host=localhost

Na produkcji PostgreSQL może działać na innym serwerze.

Przykład:

host=db.example.com

albo:

host=10.0.0.5

albo wewnątrz Dockera:

host=postgres

Host zależy od tego, gdzie mieszka baza danych.

Aplikacja potrzebuje poprawnego adresu.

Inaczej będzie pukać do złych drzwi.

PostgreSQL nie odpowie z domu, w którym nie mieszka.

Bardzo rozsądne.

Port PostgreSQL

PostgreSQL zwykle nasłuchuje na porcie:

5432

Dlatego typowe połączenie używa:

port=5432

Port jest jak numer drzwi na serwerze.

Serwer może uruchamiać wiele usług.

PostgreSQL domyślnie używa portu 5432.

Jeśli zmieniłeś port, musisz użyć nowego.

Ale dla początkujących najczęściej odpowiedź brzmi:

5432

Rzadki moment prostoty.

Ciesz się nim.

Nazwa bazy danych

Jeden serwer PostgreSQL może zawierać wiele baz danych.

Na przykład:

learning_postgresql
shop_db
portfolio_db
blog_db

Twoja aplikacja musi wiedzieć, której bazy używać.

Przykład:

database=shop_db

Jeśli aplikacja połączy się z niewłaściwą bazą, dzieją się dziwne rzeczy.

Tabele mogą nie istnieć.

Dane mogą wyglądać na stare.

Twój mózg może zacząć debugować zły wszechświat.

Zawsze sprawdzaj nazwę bazy danych.

Mała literówka.

Duży ból głowy.

Username i password

PostgreSQL używa użytkowników i haseł do kontroli dostępu.

Przykład:

username=shop_user
password=strong_password

Użytkownik określa, co aplikacja może robić.

Użytkownik może mieć uprawnienia takie jak:

łączenie się z bazą danych
czytanie tabel
wstawianie wierszy
aktualizowanie wierszy
usuwanie wierszy
tworzenie tabel

Nie dawaj każdej aplikacji pełnych mocy superusera.

To jak dać dziecku piłę łańcuchową, bo chciało przeciąć kartkę papieru.

Technicznie skuteczne.

Głęboko nierozsądne.

Nie używaj użytkownika postgres w aplikacjach

PostgreSQL często ma domyślnego użytkownika administracyjnego:

postgres

Ten użytkownik jest potężny.

Bardzo potężny.

Zbyt potężny dla zwykłych aplikacji.

Nie używaj postgres jako użytkownika aplikacji.

Zamiast tego utwórz osobnego użytkownika dla aplikacji.

Przykład:

shop_user
blog_user
app_user
portfolio_user

Dlaczego?

Bo jeśli aplikacja ma błąd albo zostanie zaatakowana, chcesz ograniczyć szkody.

Użytkownik aplikacji powinien mieć tylko te uprawnienia, których potrzebuje.

Nie klucze do całego królestwa.

Królestwo bazy danych ma smoki.

I faktury.

Tworzenie bazy danych dla aplikacji

Otwórz PostgreSQL jako użytkownik postgres:

sudo -iu postgres psql

Utwórz bazę danych:

CREATE DATABASE shop_db;

Utwórz użytkownika:

CREATE USER shop_user WITH PASSWORD 'change_this_password';

Daj użytkownikowi dostęp do bazy:

GRANT CONNECT ON DATABASE shop_db TO shop_user;

Teraz połącz się z bazą:

\c shop_db

Daj uprawnienia do schematu public:

GRANT USAGE ON SCHEMA public TO shop_user;

Pozwól użytkownikowi pracować z tabelami:

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

Pozwól użytkownikowi używać sekwencji.

To ważne dla ID typu SERIAL.

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

Dla przyszłych tabel możesz ustawić domyślne uprawnienia:

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;

To już dużo lepsze niż używanie superusera postgres.

Aplikacja dostaje dostęp.

Ale nie nieograniczoną władzę.

Bardzo cywilizowane.

Testowanie nowego użytkownika

Wyjdź z PostgreSQL:

\q

Teraz spróbuj połączyć się jako nowy użytkownik:

psql -h localhost -U shop_user -d shop_db

PostgreSQL zapyta o hasło.

Wpisz hasło, które utworzyłeś.

Jeśli połączenie działa, wejdziesz do psql.

Potem przetestuj:

SELECT current_user;

Powinieneś zobaczyć:

shop_user

Dobrze.

Teraz twoja aplikacja też może używać tego użytkownika.

Drzwi bazy danych się otwierają.

Ale tylko z właściwym kluczem.

Bardzo bezpiecznie.

Bardzo dorośle.

Czym jest connection string?

Connection string umieszcza wszystkie informacje połączeniowe w jednej linii.

Typowy format PostgreSQL:

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

Przykład:

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

Zawiera:

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

Wiele narzędzi i frameworków używa connection stringów.

Są kompaktowe.

Przydatne.

I niebezpieczne, jeśli wklejasz je wszędzie.

Bo zawierają hasła.

Traktuj connection stringi jak klucze.

Nie publikuj ich.

Nie commituj ich na GitHuba.

Nie wysyłaj ich przypadkowym ludziom.

Nie wkładaj ich do screenshotów.

Przyszły ty ci podziękuje.

Ludzie od bezpieczeństwa też przestaną krzyczeć.

Zmienne środowiskowe

Aplikacje zwykle czytają ustawienia bazy danych ze zmiennych środowiskowych.

Zmienne środowiskowe to wartości przechowywane poza kodem.

Przykład:

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

Albo:

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

Dlaczego używać zmiennych środowiskowych?

Bo kod nie powinien zawierać sekretów.

Źle:

hasło bezpośrednio w kodzie źródłowym

Dobrze:

hasło w zmiennej środowiskowej

To pozwala używać różnych ustawień dla:

lokalnego developmentu
testów
produkcji

Ten sam kod.

Inna konfiguracja.

Bardzo przydatne.

Bardzo profesjonalne.

Bardzo “nauczyłem się po bolesnych błędach”.

Plik .env

Podczas lokalnego developmentu wiele projektów używa pliku .env.

Przykład:

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

Albo:

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

Aplikacja czyta ten plik przy starcie.

Ważne:

Nie commituj plików .env z prawdziwymi hasłami.

Dodaj .env do .gitignore.

Przykład .gitignore:

.env
.env.local
.env.production

Możesz stworzyć bezpieczny plik przykładowy:

.env.example

Przykład:

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

To pokazuje innym programistom, jakie zmienne są potrzebne.

Ale nie ujawnia prawdziwych sekretów.

Dobra praktyka.

Bardzo dobra.

Złota gwiazdka.

Bazodanowa złota gwiazdka.

Przykład: ogólne ustawienia aplikacji

Wiele aplikacji potrzebuje ustawień takich jak:

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

Potem aplikacja tworzy połączenie.

Koncepcyjnie:

Połącz się z DB_HOST na porcie DB_PORT.
Użyj DB_NAME.
Zaloguj się przez DB_USER i DB_PASSWORD.

Niektóre frameworki wolą jeden URL:

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

Oba style są popularne.

Którego użyjesz, zależy od frameworka.

Ale idea jest ta sama.

Twoja aplikacja potrzebuje wskazówek, gdzie jest baza danych.

Bez wskazówek błąka się jak turysta z zepsutym GPS-em.

Przykład: ustawienia Django

W Django ustawienia bazy danych często wyglądają tak:

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

Ale wpisywanie hasła bezpośrednio tutaj nie jest dobrym pomysłem.

Lepiej tak:

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"),
    }
}

Teraz hasło pochodzi ze zmiennych środowiskowych.

Dużo lepiej.

Django dostaje dane połączeniowe.

Twój kod zostaje bezpieczniejszy.

Wszyscy są mniej nerwowi.

Może oprócz developera CSS.

Ale to inny kurs.

Przykład: ustawienia Spring Boot

W Spring Boot application.properties może wyglądać tak:

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

Lepiej ze zmiennymi środowiskowymi:

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

Wtedy środowisko może zawierać:

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

To dużo bezpieczniejsze niż hardcodowanie sekretów.

W prawdziwych projektach hasło bazy danych nie powinno mieszkać w repozytorium Git.

Git pamięta wszystko.

Jak słoń.

Ale z historią commitów.

Przykład: połączenie Node.js

Aplikacja Node.js może używać connection stringa:

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

Potem aplikacja go czyta.

Koncepcyjnie:

const connectionString = process.env.DATABASE_URL;

Wiele bibliotek Node.js wspiera taki styl.

Ważna idea nie polega na konkretnej bibliotece.

Ważna idea brzmi:

Czytaj sekrety ze zmiennych środowiskowych.
Nie wpisuj ich bezpośrednio do kodu.

Ta zasada idzie za tobą przez wszystkie języki.

Python.

Java.

JavaScript.

Go.

PHP.

Wszędzie.

Sekrety w kodzie są jak otwarte okna zimą.

Zły pomysł.

Później kosztowny.

Lokalny development vs produkcja

Lokalny development zwykle oznacza:

aplikacja i baza danych działają na twoim komputerze
host = localhost
port = 5432

Produkcja zwykle oznacza:

aplikacja działa na serwerze
baza danych może działać na innym serwerze
host nie jest localhost
hasło jest silniejsze
bezpieczeństwo ma większe znaczenie
backupy mają większe znaczenie
monitoring ma większe znaczenie

W lokalnym developmencie błędy są irytujące.

Na produkcji błędy stają się fakturami.

Bardzo motywujące.

Dlatego konfiguracja powinna być elastyczna.

Możesz mieć:

lokalną bazę danych
bazę testową
bazę produkcyjną

Każde środowisko ma inne wartości.

Ten sam kod aplikacji.

Różne zmienne środowiskowe.

To normalne.

To profesjonalne.

To pozwala nie zmieniać kodu tylko po to, żeby połączyć się gdzie indziej.

Docker i nazwy hostów PostgreSQL

Jeśli aplikacja i PostgreSQL działają w Docker Compose, hostem często jest nazwa serwisu.

Przykład:

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

  postgres:
    image: postgres

Wewnątrz Dockera aplikacja może łączyć się z:

postgres

a nie z:

localhost

Dlaczego?

Bo wewnątrz kontenera aplikacji localhost oznacza sam kontener aplikacji.

Nie kontener PostgreSQL.

To bardzo częsty błąd.

Bardzo częsty.

Bardzo bolesny.

Bardzo Docker.

Prosta zasada:

Z twojego komputera: localhost może działać.
Z innego kontenera: użyj nazwy serwisu.

Więc w Docker Compose host bazy danych może być:

postgres

albo:

db

zależnie od nazwy serwisu.

Networking Dockera jest potężny.

I czasem pikantny.

Typowe błędy połączenia

Connection refused

Przykładowy błąd:

connection refused

Zwykle oznacza:

PostgreSQL nie działa.
Zły host.
Zły port.
PostgreSQL tam nie nasłuchuje.
Firewall blokuje połączenie.

Sprawdź status PostgreSQL:

systemctl status postgresql

Na niektórych systemach nazwa usługi może być inna.

Możesz też spróbować:

psql -h localhost -U shop_user -d shop_db

Jeśli psql nie może się połączyć, aplikacja prawdopodobnie też nie może.

Nie obwiniaj frameworka od razu.

Najpierw sprawdź bazę danych.

Frameworki często są winne.

Ale nie zawsze.

Password authentication failed

Przykładowy błąd:

password authentication failed for user

Zwykle oznacza:

zły username
złe hasło
użytkownik nie istnieje
zmienna środowiskowa ma złą wartość

Sprawdź username:

SELECT current_user;

Sprawdź, czy użytkownik istnieje:

\du

Jeśli trzeba, zresetuj hasło:

ALTER USER shop_user WITH PASSWORD 'new_password_here';

Potem zaktualizuj zmienną środowiskową.

Nie aktualizuj tylko swojej pamięci.

Aplikacje nie potrafią czytać twojej pamięci.

Na szczęście.

Database does not exist

Przykładowy błąd:

database "shop_db" does not exist

To zwykle oznacza dokładnie to, co mówi.

Sprawdź bazy danych:

\l

Utwórz bazę, jeśli trzeba:

CREATE DATABASE shop_db;

Albo popraw nazwę bazy w konfiguracji.

Ten błąd często jest tylko literówką.

Mała literówka z dużą osobowością.

Permission denied

Przykładowy błąd:

permission denied for table products

To oznacza, że użytkownik połączył się poprawnie.

Ale nie ma uprawnień do wykonania czegoś.

Daj uprawnienia:

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

Dla sekwencji:

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

Jeśli aplikacja może czytać, ale nie może wstawiać wierszy z ID SERIAL, problemem mogą być uprawnienia do sekwencji.

Uprawnienia PostgreSQL są potężne.

I trochę irytujące.

Jak poważny ochroniarz z papierologią.

Table does not exist

Przykładowy błąd:

relation "products" does not exist

Możliwe powody:

Połączyłeś się z niewłaściwą bazą danych.
Tabela nie została utworzona.
Tabela jest w innym schemacie.
Migracja nie została uruchomiona.
Nazwa tabeli jest inna.

Sprawdź tabele:

\dt

Sprawdź aktualną bazę danych:

SELECT current_database();

Ten błąd często zdarza się, kiedy aplikacja łączy się z jedną bazą, a ty stworzyłeś tabele w innej.

Klasyk.

Bolesny.

Bardzo edukacyjny.

Podstawowe zasady bezpieczeństwa

Oto proste zasady.

Nie zaawansowane bezpieczeństwo.

Podstawowe przetrwanie.

Nie wpisuj haseł do kodu

Źle:

hasło w kodzie źródłowym

Dobrze:

hasło w zmiennej środowiskowej

Kod można udostępniać.

Sekretów nie.

Bardzo proste.

Bardzo ignorowane.

Bardzo niebezpieczne.

Nie commituj .env

Dodaj .env do .gitignore.

.env
.env.local
.env.production

Jeśli przypadkiem commitujesz prawdziwe hasło, zmień je.

Nie wystarczy usunąć plik z ostatniego commita i udawać, że nic się nie stało.

Historia Git pamięta.

Git nie jest twoim przyjacielem w tej sytuacji.

Git jest świadkiem.

Używaj osobnego użytkownika bazy danych

Nie używaj superusera postgres w aplikacji.

Utwórz użytkownika aplikacji:

shop_user

Daj mu tylko potrzebne uprawnienia.

To ogranicza szkody, jeśli coś pójdzie źle.

A coś kiedyś pójdzie źle.

To nie pesymizm.

To rozwój oprogramowania.

Używaj mocnych haseł

Nie używaj:

password
123456
postgres
admin
qwerty

To nie są hasła.

To zaproszenia.

Używaj mocnych haseł.

Zwłaszcza na produkcji.

Twoja baza danych zasługuje na więcej.

Trzymaj produkcyjną bazę prywatnie

Produkcyjna baza PostgreSQL zwykle nie powinna być otwarta na cały internet.

Lepiej:

sieć prywatna
reguły firewall
ograniczony dostęp po IP
bezpieczna konfiguracja hostingu

Jeśli baza danych jest publicznie dostępna, bezpieczeństwo robi się dużo poważniejsze.

Bazy danych lubią prywatność.

Użytkownicy też.

Prawnicy też.

Migracje aplikacji

Wiele frameworków używa migracji.

Migracja to kontrolowana zmiana w bazie danych.

Przykłady:

utworzenie tabeli
dodanie kolumny
zmiana typu kolumny
utworzenie indeksu
usunięcie tabeli

Zamiast ręcznie zmieniać bazę za każdym razem, framework śledzi zmiany.

Django ma migracje.

Spring Boot często używa narzędzi takich jak Flyway albo Liquibase.

ORM-y Node.js też mają narzędzia migracji.

Idea brzmi:

Struktura bazy danych powinna być wersjonowana.

To ważne, bo aplikacja i baza danych muszą do siebie pasować.

Jeśli kod oczekuje kolumny email, a baza jej nie ma, aplikacja się psuje.

Bardzo pewnie siebie.

Migracje pomagają kontrolować strukturę.

Nie są magią.

Ale są lepsze niż losowe ręczne zmiany o północy.

ORM vs czysty SQL

Wiele aplikacji używa ORM.

ORM oznacza Object-Relational Mapping.

Przykłady:

Django ORM
Hibernate / JPA
Prisma
TypeORM
Sequelize
SQLAlchemy

ORM pozwala pracować z wierszami bazy danych jak z obiektami.

Przykładowa idea:

obiekt Product
obiekt Customer
obiekt Order

ORM generuje SQL.

To wygodne.

Ale nadal musisz rozumieć SQL.

Dlaczego?

Bo kiedy coś robi się wolne albo się psuje, ORM nie uratuje twojej duszy.

Wygeneruje SQL.

PostgreSQL wykona SQL.

Problemy wydajności nadal są problemami SQL.

Więc nauka SQL nie jest stratą czasu.

Nawet jeśli używasz ORM.

Właściwie szczególnie wtedy, gdy używasz ORM.

Bo wiesz, co dzieje się pod kocem.

A czasem pod kocem jest potworne zapytanie.

Typowy przepływ aplikacji

Typowy przepływ backendu wygląda tak:

Użytkownik otwiera stronę.
Frontend wysyła request do backendu.
Backend łączy się z PostgreSQL.
Backend wykonuje zapytanie.
PostgreSQL zwraca dane.
Backend zwraca JSON albo HTML.
Frontend pokazuje wynik.

Przykład:

GET /products

Zapytanie backendu:

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;

Backend zwraca JSON:

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

Tak PostgreSQL staje się częścią aplikacji.

Użytkownik nigdy nie widzi SQL.

Ale SQL wykonuje pracę.

Jak pracownik za kulisami.

Cichy.

Ważny.

Trochę niedoceniany.

Connection pooling

Otwarcie połączenia z bazą danych zajmuje czas.

Jeśli każdy request otwiera nowe połączenie i od razu je zamyka, wydajność może ucierpieć.

Wiele aplikacji używa connection pool.

Connection pool utrzymuje kilka otwartych połączeń z bazą i używa ich ponownie.

Prosta idea:

Nie twórz nowego połączenia za każdym razem.
Używaj ponownie istniejących połączeń.

Większość frameworków obsługuje to za ciebie.

Ale warto znać tę ideę.

Bo systemy produkcyjne bardzo przejmują się połączeniami.

Zbyt wiele połączeń może przeciążyć PostgreSQL.

Zbyt mało połączeń może spowolnić aplikację.

Równowaga ma znaczenie.

Jak kawa.

Za mało źle.

Za dużo i słyszysz kolory.

Praktyka

Utwórz nową bazę danych:

CREATE DATABASE app_demo_db;

Utwórz nowego użytkownika:

CREATE USER app_demo_user WITH PASSWORD 'change_this_password';

Daj prawo połączenia:

GRANT CONNECT ON DATABASE app_demo_db TO app_demo_user;

Połącz się z bazą:

\c app_demo_db

Utwórz prostą tabelę:

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

Daj uprawnienia do schematu:

GRANT USAGE ON SCHEMA public TO app_demo_user;

Daj uprawnienia do tabel:

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

Daj uprawnienia do sekwencji:

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

Wstaw testowy wiersz:

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

Przetestuj połączenie:

psql -h localhost -U app_demo_user -d app_demo_db

Potem uruchom:

SELECT * FROM messages;

Jeśli to działa, masz bazę danych, użytkownika, uprawnienia i działające połączenie.

To fundament integracji z aplikacjami.

Nie efektowny.

Bardzo ważny.

Jak dobra instalacja elektryczna.

Nikt jej nie widzi.

Wszyscy narzekają, kiedy nie działa.

Mini wyzwanie

Utwórz bazę danych dla aplikacji blogowej.

Wymagania:

nazwa bazy danych: blog_app_db
nazwa użytkownika: blog_app_user
tabele: authors, posts

Zasady:

Utwórz bazę danych:

CREATE DATABASE blog_app_db;

Utwórz użytkownika:

CREATE USER blog_app_user WITH PASSWORD 'change_this_password';

Daj prawo połączenia:

GRANT CONNECT ON DATABASE blog_app_db TO blog_app_user;

Połącz się:

\c blog_app_db

Utwórz tabele:

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
);

Daj uprawnienia:

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;

Utwórz przydatne indeksy:

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

Teraz utwórz plik .env.example dla aplikacji:

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

Albo:

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

Potem odpowiedz:

Które wartości można bezpiecznie commitować?
Które wartości powinny zostać sekretami?
Dlaczego .env powinien być ignorowany przez Git?
Dlaczego aplikacja nie powinna używać użytkownika postgres?

To praktyczne.

To realne.

To nudny fundament, który zapobiega ekscytującym katastrofom.

A w bazach danych nudne często jest dobre.

Bardzo dobre.

Typowe błędy

Używanie postgres wszędzie

Nie rób tego.

Użytkownik postgres służy do administracji.

Twoja aplikacja powinna mieć własnego użytkownika.

Daj mu tylko potrzebne uprawnienia.

To podstawowe bezpieczeństwo.

Podstawowe bezpieczeństwo nie jest opcjonalne.

To jak noszenie butów w warsztacie.

Wpisywanie sekretów do kodu

Źle:

spring.datasource.password=my_real_password

Źle:

"PASSWORD": "my_real_password"

Źle:

const password = "my_real_password";

Używaj zmiennych środowiskowych.

Twój kod nie jest sejfem.

Zapominanie o uprawnieniach do sekwencji

Jeśli aplikacja może wstawiać wiersze, ale dostaje błędy uprawnień do sekwencji, pamiętaj:

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

SERIAL używa sekwencji.

Sekwencje potrzebują uprawnień.

PostgreSQL jest precyzyjny.

Czasem boleśnie precyzyjny.

Łączenie się z niewłaściwą bazą danych

Zawsze sprawdzaj:

SELECT current_database();

Wiele błędów to po prostu:

Utworzyłem tabelę w jednej bazie.
Moja aplikacja łączy się z inną bazą.

To boli.

Ale jest bardzo częste.

Złe używanie localhost w Dockerze

W Docker Compose localhost z kontenera aplikacji zwykle oznacza kontener aplikacji.

Nie kontener bazy danych.

Użyj nazwy serwisu bazy danych.

Przykład:

postgres

albo:

db

Docker się nie myli.

Jest tylko bardzo dosłowny.

Jak PostgreSQL.

Pewnie dobrze się dogadują.

Podsumowanie

Dzisiaj nauczyłeś się:

To ważna lekcja.

Teraz rozumiesz, jak PostgreSQL łączy się z prawdziwymi aplikacjami.

Tutaj wiedza o bazie danych staje się backendową mocą.

Baza danych przechowuje prawdę.

Aplikacja używa prawdy.

Użytkownik widzi wynik.

A gdzieś pośrodku programista ma nadzieję, że zmienne środowiskowe są poprawne.

Bardzo realne.

Bardzo profesjonalne.

Bardzo poniedziałkowe.

Następna lekcja

W następnej lekcji porozmawiamy o backupach, restore i podstawowej konserwacji bazy danych.

Bo stworzenie bazy danych jest dobre.

Używanie bazy danych jest lepsze.

Ale możliwość odzyskania danych po problemie jest bezcenna.

Backupy są nudne.

Aż do dnia, kiedy ratują ci życie.

Wtedy stają się piękne.