π PostgreSQL Features and Setup
PostgreSQL is the database that grows with you β free, wildly capable, and trusted by everything from weekend projects to some of the largest systems on the planet. In this lesson you'll install it, learn what makes it special, meet its unusually rich set of data types, and configure it safely for local development.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain what an object-relational database is and why PostgreSQL is a strong default choice
- Install PostgreSQL on Windows, macOS, or Linux and connect with
psql - Create a database, a dedicated role, and a table with correct data types and constraints
- Use PostgreSQL's advanced types β
JSONB, arrays, and ranges β in real queries - Locate and safely edit the key configuration files (
postgresql.conf,pg_hba.conf)
Estimated Time: 45β60 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Stand up a development database and schema for a small blog, entirely from the command line.
In This Lesson
What Is PostgreSQL?
PostgreSQL (say "post-gres-cue-ell", or just "Postgres") is a free, open-source object-relational database management system with more than 35 years of active development behind it. "Relational" means it stores data in tables of rows and columns with enforced relationships between them; "object-relational" means it goes further β letting you define custom types, functions, and even index strategies. It has a well-earned reputation for correctness, reliability, and never losing your data.
π‘ An analogy β the Swiss Army knife of databases: A simpler database is like a single good kitchen knife: fast and easy for one job. PostgreSQL is the Swiss Army knife β it has more tools than you'll need on day one (arrays, JSON, full-text search, geospatial data), but as your project grows you'll reach for them one by one and be glad they were already in your pocket.
π Key Terms
RDBMS: Relational Database Management System β the software that stores, queries, and protects your tables of data.
ACID: Atomicity, Consistency, Isolation, Durability β the four guarantees that a well-behaved transaction either fully happens or fully doesn't, and once committed, stays committed.
MVCC: Multi-Version Concurrency Control β how Postgres lets many users read and write at once without readers blocking writers.
Key features at a glance
- ACID compliance: transactions are reliable β you never end up with money debited from one account but not credited to another.
- Rich data types: beyond the basics, native support for
JSONB, arrays, ranges, UUIDs, network addresses, and geometric types. - Extensibility: add capabilities with extensions like
PostGIS(maps),pg_trgm(fuzzy text), orpgvector(AI embeddings). - Concurrency: MVCC means readers don't block writers and writers don't block readers.
- Full-text search and window functions built in β features you'd otherwise bolt on with extra services.
- Cross-platform & free: the permissive PostgreSQL License means no per-seat costs, ever.
PostgreSQL vs. Other Databases
No database is "best" for everything. Here's how PostgreSQL compares to the other names you'll hear most often, so you can pick with intent rather than habit.
| Feature | PostgreSQL | MySQL | SQLite | MongoDB |
|---|---|---|---|---|
| Model | Object-relational | Relational | Relational (embedded) | Document (NoSQL) |
| License | PostgreSQL (permissive) | GPL / commercial | Public domain | SSPL |
| Data types | Extensive, extensible | Standard | Limited (dynamic) | BSON documents |
| Complex queries | Excellent | Good | Basic | Aggregation pipeline |
| Concurrency | MVCC (high) | Row locks (InnoDB) | File-level lock | Document-level |
| JSON support | Native, indexable (JSONB) | Good (JSON type) | Basic (JSON1) | Native |
| Best for | Complex data, integrity, growth | Read-heavy web apps | Local / embedded apps | Flexible, evolving schemas |
β When to reach for PostgreSQL
- Data integrity matters β finance, healthcare, anything where a wrong number is unacceptable.
- Your queries are getting complex β joins, aggregates, window functions, CTEs.
- You want room to grow β start relational, add JSON columns or geospatial data later without switching databases.
- You're unsure β honestly, "just use Postgres" is sound default advice for most new backends.
Installing PostgreSQL
Pick the tab that matches your machine. You want a recent major version β PostgreSQL 16 or 17 at the time of writing.
Windows
- Download the installer from the official EDB installer page.
- Run it and install the Server, pgAdmin (GUI), and Command Line Tools.
- Set a password for the
postgressuperuser β write it down. - Accept the default port
5432and default locale.
macOS (Homebrew β recommended for developers)
# Install Homebrew if you don't have it, then:
brew install postgresql@16
# Start the service (and restart it on login)
brew services start postgresql@16
# Create a database named after your macOS user for convenient access
createdb "$(whoami)"
Prefer a click-to-run app instead? Postgres.app bundles everything and adds a menu-bar toggle.
Linux (Ubuntu / Debian)
# Update the package index and install
sudo apt update
sudo apt install postgresql postgresql-contrib
# Start now and enable on boot
sudo systemctl enable --now postgresql
# Become the postgres OS user and open a superuser prompt
sudo -u postgres psql
β οΈ Installation gotchas
- Remember the
postgrespassword. Resetting it later means editingpg_hba.confβ avoidable pain. - Don't build your app as the
postgressuperuser. Create a dedicated, limited role (you'll do this below). - Only change the port
5432if something else already uses it.
A quick way to confirm the whole thing works, once installed:
psql -c "SELECT version();"
Expected output (abridged):
PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc ...
Your First Session with psql
psql is the interactive terminal for PostgreSQL. It runs both SQL statements (which end in a semicolon) and backslash meta-commands (like \dt) that inspect the database quickly.
Connecting
# Full form
psql -U username -d database_name -h localhost -p 5432
# Common short forms
psql # connect as the current OS user
psql -d mydb # connect to a specific database
psql "postgresql://webuser:secret@localhost:5432/appdb" # connection URL
Meta-commands worth memorizing
\l -- list all databases
\c dbname -- connect to (switch to) a database
\dt -- list tables in the current schema
\d tablename -- describe a table's columns and indexes
\du -- list roles (users)
\dn -- list schemas
\dx -- list installed extensions
\x -- toggle expanded (row-per-line) display
\timing on -- show how long each query takes
\? -- help for meta-commands
\q -- quit
Creating a database and a dedicated role
This is the everyday setup: a role for your app with a password, a database it owns, and a table. Notice GENERATED ALWAYS AS IDENTITY β the modern, SQL-standard replacement for the older SERIAL keyword.
-- Run these as the postgres superuser
CREATE ROLE appuser WITH LOGIN PASSWORD 'change_me_in_prod';
CREATE DATABASE blog OWNER appuser;
-- Connect to the new database
\c blog
-- Modern identity column instead of SERIAL
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Let appuser use this table
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser;
The CRUD cycle in SQL
-- Create
INSERT INTO users (username, email)
VALUES ('johndoe', 'john@example.com')
RETURNING id, created_at; -- RETURNING hands back the generated values
-- Read
SELECT id, username, email FROM users WHERE username = 'johndoe';
-- Update
UPDATE users SET email = 'john.doe@example.com' WHERE username = 'johndoe';
-- Delete
DELETE FROM users WHERE username = 'johndoe';
π‘ Backup & restore in one breath
Every database you care about needs a backup habit. PostgreSQL ships the tools:
# Back up a whole database to a compressed custom-format file
pg_dump -U appuser -F c -f blog.dump blog
# Restore it into a fresh database
createdb -U appuser blog_restored
pg_restore -U appuser -d blog_restored blog.dump
Data Types That Set It Apart
Choosing the right column type is one of the highest-leverage decisions in database design. Start with the everyday types, then meet the three that make people fall in love with Postgres.
| Category | Use this | Notes |
|---|---|---|
| Whole numbers | INTEGER, BIGINT | Use BIGINT for IDs that may exceed ~2 billion. |
| Money / exact decimals | NUMERIC(p,s) | Never use float for currency β rounding errors. |
| Text | TEXT or VARCHAR(n) | TEXT is fine and fast; add a length limit only when the rule is real. |
| True / false | BOOLEAN | Stores true, false, or NULL. |
| Timestamps | TIMESTAMPTZ | Prefer the timezone-aware variant almost always. |
| Unique IDs | UUID | Great for distributed / public-facing identifiers. |
β οΈ TIMESTAMP vs TIMESTAMPTZ
Plain TIMESTAMP stores a wall-clock time with no timezone β a recipe for bugs when users span the globe. Default to TIMESTAMPTZ, which stores an unambiguous moment in time and converts on display.
1. JSONB β structured documents inside a relational table
JSONB stores JSON in a binary, indexable form. It's perfect for flexible, semi-structured data (settings, event payloads, API responses) without abandoning SQL.
CREATE TABLE events (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
metadata JSONB NOT NULL
);
INSERT INTO events (name, metadata) VALUES
('page_view', '{"page": "/home", "user_id": 123, "device": {"os": "iOS"}}');
-- ->> extracts a field as text; -> keeps it as JSON
SELECT name, metadata->>'page' AS page
FROM events
WHERE metadata->>'page' = '/home';
-- Reach into nested objects
SELECT metadata->'device'->>'os' AS operating_system FROM events;
-- @> asks "does this JSON contain that JSON?" (great with a GIN index)
SELECT * FROM events WHERE metadata @> '{"user_id": 123}';
-- Merge new keys in with the || operator
UPDATE events SET metadata = metadata || '{"processed": true}'::jsonb WHERE id = 1;
2. Arrays β a list in a single column
CREATE TABLE products (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
tags TEXT[] -- an array of text values
);
INSERT INTO products (name, tags)
VALUES ('Ergonomic Keyboard', ARRAY['electronics', 'office', 'ergonomic']);
-- Arrays are 1-indexed
SELECT name, tags[1] AS first_tag FROM products;
-- Does the array contain a value?
SELECT * FROM products WHERE 'office' = ANY(tags);
-- Append to an array
UPDATE products SET tags = array_append(tags, 'bestseller') WHERE id = 1;
3. Ranges β a from/to pair with overlap logic built in
CREATE TABLE reservations (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room_id INTEGER NOT NULL,
during TSTZRANGE NOT NULL
);
INSERT INTO reservations (room_id, during)
VALUES (7, tstzrange('2026-08-01 12:00+00', '2026-08-01 14:00+00'));
-- && means "do these two ranges overlap?" β perfect for double-booking checks
SELECT * FROM reservations
WHERE during && tstzrange('2026-08-01 13:00+00', '2026-08-01 15:00+00');
β The payoff
These three types let one relational table absorb responsibilities that would otherwise push you toward a second, separate NoSQL system. You get flexibility and SQL joins, constraints, and transactions.
Configuration Essentials
Two files control almost everything you'll care about early on:
postgresql.confβ server behavior: memory, connections, logging.pg_hba.confβ "host-based authentication": who may connect, from where, and how.
Don't hunt through the filesystem β ask the server where they live:
SHOW config_file; -- path to postgresql.conf
SHOW hba_file; -- path to pg_hba.conf
A few parameters worth knowing
| Parameter | What it does | Sensible dev value |
|---|---|---|
listen_addresses | Which network interfaces to accept connections on | 'localhost' |
max_connections | Concurrent connection cap | 100 (use a pooler beyond that) |
shared_buffers | Memory Postgres uses for caching data | ~25% of system RAM |
work_mem | Memory per sort/hash operation | 16MB |
random_page_cost | How expensive the planner thinks random reads are | 1.1 on SSDs |
log_min_duration_statement | Log queries slower than N ms | 1000 (find slow queries) |
pg_hba.conf β the front door
Each line is read top-to-bottom; the first match wins. Columns are: connection type, database, user, address, and authentication method.
# TYPE DATABASE USER ADDRESS METHOD
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
β οΈ Authentication methods, from worst to best
trustβ no password at all. Never use outside a throwaway container.md5β legacy password hashing; superseded.scram-sha-256β the modern default. Use this.
After editing pg_hba.conf, reload without a restart: SELECT pg_reload_conf();
GUI tools, if you prefer clicking
You never need a GUI, but they help with exploration: pgAdmin (bundled, full-featured), DBeaver (free, multi-database), TablePlus and Beekeeper Studio (modern, lightweight). Learn psql first, though β it's always available, even on a bare server over SSH.
Hands-on Exercise
ποΈ Build a blog database from scratch
Objective: Create a role, a database, and a small schema β then prove it works with a JSONB query.
Instructions:
- As the
postgressuperuser, create a roleblogadminwith a login and password, and a databasemyblogthat it owns. - Connect to
myblogand create apoststable with: an identity primary key, atitle(text, required), abody(text), atagsarray, adetailsJSONB column, and apublished_attimestamptz. - Insert two posts, one with tags
['sql','postgres']and adetailsvalue of{"featured": true}. - Write a query that returns only the posts where
detailscontains{"featured": true}. - Write a query that returns every post tagged
'postgres'.
π‘ Hint
Use GENERATED ALWAYS AS IDENTITY for the key, TEXT[] for tags, and JSONB for details. The two queries use the @> containment operator and the ANY(...) array test respectively.
β Solution
-- Step 1 (as postgres)
CREATE ROLE blogadmin WITH LOGIN PASSWORD 'dev_only_password';
CREATE DATABASE myblog OWNER blogadmin;
\c myblog
-- Step 2
CREATE TABLE posts (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
body TEXT,
tags TEXT[],
details JSONB NOT NULL DEFAULT '{}',
published_at TIMESTAMPTZ
);
-- Step 3
INSERT INTO posts (title, body, tags, details, published_at) VALUES
('Why Postgres', 'A love letter.', ARRAY['sql','postgres'],
'{"featured": true}', now()),
('Draft idea', 'Not ready yet.', ARRAY['meta'],
'{"featured": false}', NULL);
-- Step 4: featured posts
SELECT id, title FROM posts WHERE details @> '{"featured": true}';
-- Step 5: posts tagged 'postgres'
SELECT id, title FROM posts WHERE 'postgres' = ANY(tags);
π― Quick Quiz
Question 1: Which column type should you use for a flexible, indexable JSON document inside a table?
Question 2: Which file controls who may connect to PostgreSQL and how they authenticate?
Question 3: What is the modern, recommended authentication method to use in pg_hba.conf?
Best Practices
β Do
- Create a dedicated, least-privilege role per application instead of using
postgres. - Default to
TIMESTAMPTZandTEXT; add constraints only where a real rule exists. - Use
GENERATED ALWAYS AS IDENTITYfor surrogate keys on new tables. - Keep a backup habit with
pg_dump, and actually test a restore. - Turn on
log_min_duration_statementto catch slow queries early.
β οΈ Don't
- Don't store money in
float/REALβ useNUMERIC. - Don't ship
trustauthentication or a blank superuser password. - Don't reach for a separate NoSQL database before trying a
JSONBcolumn. - Don't hardcode credentials in code β that's the topic of the next lesson.
Summary & Quiz
π Key Takeaways
- PostgreSQL is a free, standards-compliant, object-relational database β a superb default for new backends.
- Installation is a one-liner on macOS/Linux and a guided installer on Windows; verify with
SELECT version();. psqlruns SQL (ends in;) and meta-commands (\dt,\d,\l) for fast inspection.- Its standout types β
JSONB, arrays, ranges β bring NoSQL-style flexibility without leaving SQL. postgresql.conftunes the server;pg_hba.confguards the door β usescram-sha-256.
π Further Reading
- PostgreSQL Official Documentation
- Chapter 8 β Data Types
- PG Exercises β practice SQL in the browser
π What's Next?
You have a running database and know how to shape data inside it. Next, Python and PostgreSQL Integration connects a real Python program to this database β running queries safely, managing transactions, and moving data in bulk.
π Well done!
PostgreSQL is now installed, understood, and ready. Let's put a program in front of it.