Skip to main content

🐘 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

Five pillars of PostgreSQL PostgreSQL sits at the center, connected to five feature groups: data integrity, extensibility, performance, standards compliance, and advanced features. Postgre SQL Data Integrity ACID Β· FK Β· checks Extensibility types Β· extensions Performance MVCC Β· indexing Standards SQL Β· JSON Advanced search Β· PostGIS
Figure 1 β€” PostgreSQL's strengths cluster into five areas. Most databases are strong in one or two; Postgres is genuinely good across all five, which is why it's such a safe default.
  • 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), or pgvector (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
ModelObject-relationalRelationalRelational (embedded)Document (NoSQL)
LicensePostgreSQL (permissive)GPL / commercialPublic domainSSPL
Data typesExtensive, extensibleStandardLimited (dynamic)BSON documents
Complex queriesExcellentGoodBasicAggregation pipeline
ConcurrencyMVCC (high)Row locks (InnoDB)File-level lockDocument-level
JSON supportNative, indexable (JSONB)Good (JSON type)Basic (JSON1)Native
Best forComplex data, integrity, growthRead-heavy web appsLocal / embedded appsFlexible, 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

  1. Download the installer from the official EDB installer page.
  2. Run it and install the Server, pgAdmin (GUI), and Command Line Tools.
  3. Set a password for the postgres superuser β€” write it down.
  4. Accept the default port 5432 and 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 postgres password. Resetting it later means editing pg_hba.conf β€” avoidable pain.
  • Don't build your app as the postgres superuser. Create a dedicated, limited role (you'll do this below).
  • Only change the port 5432 if 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.

CategoryUse thisNotes
Whole numbersINTEGER, BIGINTUse BIGINT for IDs that may exceed ~2 billion.
Money / exact decimalsNUMERIC(p,s)Never use float for currency β€” rounding errors.
TextTEXT or VARCHAR(n)TEXT is fine and fast; add a length limit only when the rule is real.
True / falseBOOLEANStores true, false, or NULL.
TimestampsTIMESTAMPTZPrefer the timezone-aware variant almost always.
Unique IDsUUIDGreat 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

ParameterWhat it doesSensible dev value
listen_addressesWhich network interfaces to accept connections on'localhost'
max_connectionsConcurrent connection cap100 (use a pooler beyond that)
shared_buffersMemory Postgres uses for caching data~25% of system RAM
work_memMemory per sort/hash operation16MB
random_page_costHow expensive the planner thinks random reads are1.1 on SSDs
log_min_duration_statementLog queries slower than N ms1000 (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:

  1. As the postgres superuser, create a role blogadmin with a login and password, and a database myblog that it owns.
  2. Connect to myblog and create a posts table with: an identity primary key, a title (text, required), a body (text), a tags array, a details JSONB column, and a published_at timestamptz.
  3. Insert two posts, one with tags ['sql','postgres'] and a details value of {"featured": true}.
  4. Write a query that returns only the posts where details contains {"featured": true}.
  5. 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 TIMESTAMPTZ and TEXT; add constraints only where a real rule exists.
  • Use GENERATED ALWAYS AS IDENTITY for surrogate keys on new tables.
  • Keep a backup habit with pg_dump, and actually test a restore.
  • Turn on log_min_duration_statement to catch slow queries early.

⚠️ Don't

  • Don't store money in float/REAL β€” use NUMERIC.
  • Don't ship trust authentication or a blank superuser password.
  • Don't reach for a separate NoSQL database before trying a JSONB column.
  • 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();.
  • psql runs 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.conf tunes the server; pg_hba.conf guards the door β€” use scram-sha-256.

πŸ“š Further Reading

πŸš€ 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.