PostgreSQL is a relational database management system. It is open source, free, and provides high performance and data integrity even under heavy workloads. It stores data in tables, establishes relationships between them, and uses the SQL language.
In short, it is a tool used to store data in a structured way, query it, and define relationships between records. Most modern applications (web, mobile, API) rely on a database like this in the background.
Below are installation steps for three operating systems. Apply the section that matches your system. After installation, PostgreSQL will run in the background as a service.
The easiest way on macOS is to use the Homebrew package manager. If you don't have it installed, get it from brew.sh.
brew update
brew install postgresql@16
brew services start postgresql@16After installation, PostgreSQL runs as a background service. To verify the version:
postgres --versionOn Ubuntu the safest path is to install the latest version from the official APT repository.
sudo apt update
sudo apt install -y postgresql postgresql-contribPostgreSQL starts automatically after installation. Check the service status with:
sudo systemctl status postgresqlOn Windows the easiest method is the official installer. Download the EDB installer from postgresql.org/download/windows and follow these steps:
5432After installation you can connect via the SQL Shell (psql) application, or manage everything through the pgAdmin graphical interface.
PostgreSQL is a service; you can't connect to the database unless it is running. Service commands differ per operating system.
brew services start postgresql@16
brew services stop postgresql@16
brew services restart postgresql@16sudo systemctl start postgresql
sudo systemctl stop postgresql
sudo systemctl restart postgresql
sudo systemctl enable postgresqlThe last command makes the service start automatically on system boot.
On Windows, manage the service through Services.msc or via the command line:
net start postgresql-x64-16
net stop postgresql-x64-16psql is the terminal tool used to interact with PostgreSQL. You can run SQL commands directly here and inspect the database.
A superuser called postgres is created during installation. For the first connection:
sudo -u postgres psql
# or
psql -U postgresWhen connected, the prompt changes to postgres=#. From now on you can type SQL commands.
Inside psql, commands starting with \are "meta commands". They are not SQL; they are shortcuts specific to the psql tool.
\l — list all databases\c database_name — switch to the given database\dt — list tables in the current database\d table_name — show the structure of a table\du — list all users (roles)\q — quit psqlIn PostgreSQL, "user" and "role" refer to the same thing; the system treats both as a role. In production it is good practice to create a separate user with limited privileges instead of using the postgres superuser.
CREATE USER ahmet WITH PASSWORD 'strong_password';This command creates a user named ahmet with the given password. By default the user has no write permission on any database.
ALTER USER ahmet WITH PASSWORD 'new_password';For a user that requires full system-wide access (use carefully):
ALTER USER ahmet WITH SUPERUSER;DROP USER ahmet;Creating a separate database for each application is the standard approach in PostgreSQL. It keeps data isolated and makes permission management easier.
CREATE DATABASE blog_app;To create one with a specific user as the owner:
CREATE DATABASE blog_app OWNER ahmet;\c blog_appWhen successful, the prompt becomes blog_app=#. From now on commands run against this database.
Warning: This action is irreversible; data is lost permanently. Always consider taking a backup first.
DROP DATABASE blog_app;Tables are the structures where data is stored. Each column has a data type that defines what values are allowed in it.
SERIAL — auto-incrementing integer (typically used for IDs)INTEGER — whole numberNUMERIC(10, 2) — 10-digit number with 2 decimals (great for prices)VARCHAR(255) — variable-length textTEXT — text without length limitBOOLEAN — true / falseDATE — date only (2026-04-28)TIMESTAMP — date + timeJSONB — JSON data (indexable)CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);PRIMARY KEY — uniquely identifies a recordUNIQUE — value can appear only once in this columnNOT NULL — cannot be left emptyDEFAULT — default is used when no value is providedDROP TABLE users;CRUD stands for Create, Read, Update, Delete. It refers to the four basic operations you can perform on a database.
INSERT INTO users (email, name)
VALUES ('ahmet@example.com', 'Ahmet Yilmaz');To insert multiple records at once:
INSERT INTO users (email, name) VALUES
('ali@example.com', 'Ali'),
('ayse@example.com', 'Ayse'),
('mehmet@example.com', 'Mehmet');SELECT * FROM users;To fetch only specific columns:
SELECT id, email FROM users;Warning: Without WHERE, all rows are updated. This is rarely what you want.
UPDATE users
SET name = 'Ahmet Demir'
WHERE id = 1;Without WHERE, all rows are deleted.
DELETE FROM users WHERE id = 1;You usually want to see only certain records out of thousands. WHERE, ORDER BY, LIMIT, and OFFSET are used for this.
SELECT * FROM users WHERE is_active = TRUE;
SELECT * FROM users WHERE name LIKE 'A%';
SELECT * FROM users WHERE created_at > '2026-01-01';SELECT * FROM users ORDER BY created_at DESC;
SELECT * FROM users ORDER BY name ASC;Commonly used in web apps to show 10 records per page and move to the next page.
SELECT * FROM users
ORDER BY id ASC
LIMIT 10 OFFSET 20;This query returns 10 records starting from the 21st (i.e., the third page).
The power of relational databases comes from establishing relationships between tables. PRIMARY KEY uniquely identifies a record, while FOREIGN KEY references the primary key of another table.
Every post has an author (user). That means the user_id column in the posts table is bound to the id column of the users table.
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
body TEXT,
created_at TIMESTAMP DEFAULT NOW()
);REFERENCES users(id) — this column is bound to the id column of the users tableON DELETE CASCADE — when a user is deleted, all of their posts are automatically deleted as wellA JOIN combines two or more tables on a common column and returns a single result set. The most common types are INNER JOIN, LEFT JOIN, and RIGHT JOIN.
Only rows that have a match in both tables are returned.
SELECT users.name, posts.title
FROM users
INNER JOIN posts ON posts.user_id = users.id;All users are returned; the post columns are NULL for users that have no posts.
SELECT users.name, posts.title
FROM users
LEFT JOIN posts ON posts.user_id = users.id;SELECT users.name, posts.title
FROM users
RIGHT JOIN posts ON posts.user_id = users.id;All records from both tables are returned; non-matching ones get NULL on the other side.
SELECT users.name, posts.title
FROM users
FULL OUTER JOIN posts ON posts.user_id = users.id;An index is an extra data structure that allows fast lookup of values in a column. Adding an index to frequently searched columns makes SELECT queries much faster. However, every index slightly slows down INSERT and UPDATEand consumes disk space; that's why you should add them only where needed.
CREATE INDEX idx_users_email ON users(email);CREATE INDEX idx_posts_user_created
ON posts(user_id, created_at DESC);DROP INDEX idx_users_email;WHERE filtersJOIN conditions (foreign keys)ORDER BYTaking regular backups of a production database is critical. PostgreSQL ships with the pg_dump and pg_restore tools for this purpose.
pg_dump -U postgres blog_app > blog_app_backup.sqlThe command above writes a full copy of the blog_app database as a SQL file to disk.
First create the target database, then import the SQL file:
createdb -U postgres blog_app_new
psql -U postgres blog_app_new < blog_app_backup.sqlThe custom (binary) format is preferred for smaller file size and selective restore.
pg_dump -U postgres -F c blog_app > blog_app.dump
pg_restore -U postgres -d blog_app_new blog_app.dumppgAdmin is the official graphical interface tool for PostgreSQL. If you're not used to the terminal or you want to inspect tables visually, it's ideal.
brew install --cask pgadmin4pgadmin.org/download/pgadmin-4-aptlocalhost5432postgresBy this point you can install PostgreSQL, connect to it, create databases, build tables, and define relationships between them. The next step is practicing on a real application of your own.