PostgreSQL Installation and Setup: A Complete Beginner's Guide
Every app you use — Instagram, your bank's website, your company's HR system — stores data somewhere. That 'somewhere' is almost always a relational database, and PostgreSQL is one of the most trusted ones on the planet. It's free, open-source, battle-hardened by decades of production use, and it powers everything from small side projects to systems at Apple, Reddit, and Spotify. If you're learning backend development or data engineering, understanding how to get PostgreSQL running locally is literally step one.
What PostgreSQL Actually Is (Before You Install Anything)
PostgreSQL (often called 'Postgres') is a relational database management system — or RDBMS. That's a fancy way of saying it's software that stores data in organised tables, like spreadsheets that can talk to each other. It runs as a background service (a server) on your machine, quietly waiting for instructions. You then connect to it using a client — either the built-in command-line tool called psql, a graphical tool like pgAdmin, or your application's code.
Here's the mental model that makes everything click: PostgreSQL is the engine, your database is the garage it runs in, and your tables are the shelves inside that garage. When you install PostgreSQL, you're installing the engine. Everything else — databases, tables, data — you create yourself after installation.
PostgreSQL supports SQL (Structured Query Language), which is the universal language for talking to relational databases. Every command you type — whether to create a table or fetch some records — is written in SQL. The good news is that SQL reads almost like plain English, so it's one of the friendliest languages to learn.
# After installation, run this command in your terminal to confirm PostgreSQL installed correctly. # The '--version' flag asks PostgreSQL to report which version is running. # If you see a version number, the installation worked. If you see 'command not found', it didn't. psql --version # You should also check that the PostgreSQL server process is running. # On Linux and macOS this uses 'pg_isready', a built-in diagnostic tool. # It pings the server and tells you whether it's accepting connections. pg_isready # On Windows, you can check via Services (services.msc) or run: sc query postgresql-x64-16
/var/run/postgresql:5432 - accepting connections
Installing PostgreSQL on Windows, macOS, and Linux
Installation differs by operating system, but the end result is the same: a running PostgreSQL server on port 5432 (the default), a superuser account called 'postgres', and the psql command-line tool available in your terminal.
Windows: The easiest path is the EnterpriseDB graphical installer at postgresql.org/download/windows. It walks you through everything with a GUI, installs pgAdmin (a visual database browser), and sets up PostgreSQL as a Windows Service that starts automatically on boot.
macOS: The cleanest method for developers is Homebrew — the macOS package manager. If you don't have Homebrew, install it first from brew.sh. One command then handles the whole PostgreSQL setup. Homebrew also makes upgrades and uninstalls trivial later.
Linux (Ubuntu/Debian): Use apt, the built-in package manager. The PostgreSQL project maintains its own apt repository so you always get the latest stable version rather than an older distribution-packaged one.
Regardless of your OS, after installation you'll need to do two things: start the server, and switch to (or authenticate as) the default 'postgres' superuser account to run your first commands.
# ───────────────────────────────────────────── # WINDOWS (run in PowerShell as Administrator) # ───────────────────────────────────────────── # Download the installer from https://www.postgresql.org/download/windows/ # Then run it and follow the GUI steps. After installation, verify with: psql --version # ───────────────────────────────────────────── # macOS (using Homebrew) # ───────────────────────────────────────────── # Step 1: Install Homebrew if you don't have it yet /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # Step 2: Install PostgreSQL (version 16 is current stable) # 'brew install' downloads and installs the package automatically brew install postgresql@16 # Step 3: Add PostgreSQL to your PATH so terminal can find the psql command # Replace '~/.zshrc' with '~/.bash_profile' if you use bash instead of zsh echo 'export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"' >> ~/.zshrc source ~/.zshrc # Step 4: Start the PostgreSQL server. # 'brew services start' registers it so it also auto-starts on every reboot brew services start postgresql@16 # ───────────────────────────────────────────── # Linux — Ubuntu / Debian # ───────────────────────────────────────────── # Step 1: Add the official PostgreSQL apt repository # This ensures we get v16, not an outdated Ubuntu-packaged version sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' # Step 2: Import the repository signing key so apt trusts it wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - # Step 3: Refresh the package list and install PostgreSQL sudo apt-get update sudo apt-get install -y postgresql-16 # Step 4: Start the service and enable it to start on system boot sudo systemctl start postgresql sudo systemctl enable postgresql # Step 5: Confirm the service is running sudo systemctl status postgresql
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
Active: active (running) since Mon 2024-06-10 09:14:32 UTC; 3s ago
psql (PostgreSQL) 16.2
First Login, Creating a Database, and Running Your First SQL
Once PostgreSQL is installed and running, you need to connect to it. By default, PostgreSQL creates a system user and a database superuser both named 'postgres'. Think of 'postgres' as the master key that unlocks everything — use it to set things up, but don't use it for your actual app later (more on that in the Gotchas section).
On Linux, you connect by switching to the 'postgres' system user first, then launching psql. On macOS (Homebrew install), your own system user is set up as a superuser automatically, so you can just run psql directly. On Windows, psql is available in the Start Menu under PostgreSQL.
Once inside psql, you'll see the prompt 'postgres=#'. This is your SQL command center. Everything you type here gets executed against the database. SQL commands end with a semicolon — forget the semicolon and psql just waits for more input, which trips up almost every beginner at least once.
The workflow for a brand-new project is always the same: create a database, connect to it, create a table, and insert some rows. Let's do exactly that.
-- ───────────────────────────────────────────── -- STEP 1: Connect to PostgreSQL -- ───────────────────────────────────────────── -- On Linux, first switch to the postgres system user in your terminal: -- $ sudo -i -u postgres -- $ psql -- On macOS (Homebrew) or Windows, just open a terminal and run: -- $ psql -U postgres -- You should now see the psql prompt: postgres=# -- ───────────────────────────────────────────── -- STEP 2: Create a new database for a bookshop app -- ───────────────────────────────────────────── -- CREATE DATABASE makes a fresh, empty database. -- Think of this as buying a brand-new filing cabinet. CREATE DATABASE bookshop_db; -- Verify it was created. '\l' is a psql shortcut that lists all databases. \l -- ───────────────────────────────────────────── -- STEP 3: Connect to the new database -- ───────────────────────────────────────────── -- '\c' stands for 'connect'. Without this, your commands run against -- the default 'postgres' database, not your new bookshop_db. \c bookshop_db -- The prompt changes to: bookshop_db=# -- That '#' confirms you're now inside bookshop_db. -- ───────────────────────────────────────────── -- STEP 4: Create a table to store book records -- ───────────────────────────────────────────── -- A table is like a spreadsheet with defined column types. -- SERIAL = auto-incrementing integer (PostgreSQL assigns the ID automatically) -- VARCHAR(255) = text up to 255 characters -- NUMERIC(10,2) = a number with up to 2 decimal places (perfect for prices) -- NOT NULL = this column can never be left empty CREATE TABLE books ( book_id SERIAL PRIMARY KEY, -- Unique ID auto-assigned to every book title VARCHAR(255) NOT NULL, -- The book's title, required author_name VARCHAR(150) NOT NULL, -- Author's full name, required price NUMERIC(10, 2) NOT NULL, -- Price in dollars, e.g. 12.99 published_year INT -- Year published, optional ); -- ───────────────────────────────────────────── -- STEP 5: Insert some real book data -- ───────────────────────────────────────────── -- INSERT INTO adds rows to the table. -- We don't supply book_id because SERIAL handles that automatically. INSERT INTO books (title, author_name, price, published_year) VALUES ('The Pragmatic Programmer', 'David Thomas', 39.99, 2019), ('Clean Code', 'Robert Martin', 34.50, 2008), ('Designing Data-Intensive Applications', 'Martin Kleppmann', 49.99, 2017); -- ───────────────────────────────────────────── -- STEP 6: Read the data back -- ───────────────────────────────────────────── -- SELECT * means 'give me every column'. -- FROM books tells PostgreSQL which table to read from. SELECT * FROM books; -- ───────────────────────────────────────────── -- STEP 7: Create a dedicated app user (best practice) -- ───────────────────────────────────────────── -- Never connect your app directly with the 'postgres' superuser. -- Create a limited user that only has access to bookshop_db. CREATE USER bookshop_app WITH PASSWORD 'use_a_strong_password_here'; -- GRANT gives this user permission to read/write in our database GRANT ALL PRIVILEGES ON DATABASE bookshop_db TO bookshop_app; -- Also grant access to the tables inside the database \c bookshop_db GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO bookshop_app; -- Confirm the user was created \du
List of databases
Name | Owner | Encoding | Collate | Ctype
---------------+----------+----------+------------+------------
bookshop_db | postgres | UTF8 | en_US.UTF8 | en_US.UTF8
postgres | postgres | UTF8 | en_US.UTF8 | en_US.UTF8
You are now connected to database "bookshop_db" as user "postgres".
CREATE TABLE
INSERT 0 3
book_id | title | author_name | price | published_year
---------+----------------------------------------+-------------------+-------+----------------
1 | The Pragmatic Programmer | David Thomas | 39.99 | 2019
2 | Clean Code | Robert Martin | 34.50 | 2008
3 | Designing Data-Intensive Applications | Martin Kleppmann | 49.99 | 2017
(3 rows)
CREATE ROLE
GRANT
List of roles
Role name | Attributes
---------------+------------------------------------------------------------
bookshop_app |
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
Connecting to PostgreSQL from a Node.js or Python App
Running SQL in psql is great for learning and administration, but in real projects you'll connect from code — a Node.js backend, a Python data script, a Django web app. PostgreSQL doesn't care what language you use, as long as your code sends standard SQL over the network.
Every language has a 'driver' or 'adapter' — a small library that handles the low-level work of opening a connection, sending your SQL, and getting results back. In Python, the most popular driver is psycopg2. In Node.js, it's the 'pg' package.
The connection always needs five pieces of information: the host (usually 'localhost' for local development), the port (5432 by default), the database name, the username, and the password. These are called connection parameters and you should never hardcode them in your source code — use environment variables instead, which keeps passwords out of version control.
Below is a working example in both Python and Node.js that connects to the bookshop_db we created, queries the books table, and prints the results.
# ───────────────────────────────────────────── # PYTHON EXAMPLE — using psycopg2 # ───────────────────────────────────────────── # First, install the driver: # pip install psycopg2-binary import psycopg2 # The PostgreSQL driver for Python import os # Used to read environment variables safely # Read connection details from environment variables. # Set these in your terminal before running: # export DB_HOST=localhost # export DB_NAME=bookshop_db # export DB_USER=bookshop_app # export DB_PASSWORD=use_a_strong_password_here db_host = os.getenv("DB_HOST", "localhost") # Default to localhost db_name = os.getenv("DB_NAME", "bookshop_db") # Our database name db_user = os.getenv("DB_USER", "bookshop_app") # Limited app user db_password = os.getenv("DB_PASSWORD", "") # Never hardcode this try: # psycopg2.connect opens an actual TCP connection to PostgreSQL. # Until this line runs successfully, no SQL can be sent. connection = psycopg2.connect( host=db_host, dbname=db_name, user=db_user, password=db_password, port=5432 # PostgreSQL's default port ) # A cursor is like a temporary workspace for running one SQL statement. # You can have multiple cursors open on the same connection. cursor = connection.cursor() # Execute a SQL query — this does NOT return results yet, just runs the query cursor.execute("SELECT book_id, title, author_name, price FROM books ORDER BY price DESC;") # fetchall() retrieves every result row as a list of tuples all_books = cursor.fetchall() print(f"Found {len(all_books)} books in the database:\n") # Loop through the results and print each book for book_id, title, author_name, price in all_books: print(f" [{book_id}] {title} by {author_name} — ${price}") except psycopg2.OperationalError as connection_error: # This error fires when PostgreSQL isn't running, credentials are wrong, # or the database name doesn't exist. The error message tells you which. print(f"Could not connect to the database: {connection_error}") finally: # Always close the cursor and connection when you're done. # Leaving connections open wastes server resources. if 'cursor' in locals(): cursor.close() if 'connection' in locals(): connection.close() print("\nDatabase connection closed cleanly.") # ───────────────────────────────────────────── # NODE.JS EXAMPLE — using the 'pg' package # Save as connect_to_bookshop_db.js and run with: node connect_to_bookshop_db.js # First install: npm install pg # ───────────────────────────────────────────── /* const { Pool } = require('pg'); // 'Pool' manages a reusable set of connections // Pool reads from environment variables automatically if you name them correctly. // Set: DB_HOST, DB_NAME, DB_USER, DB_PASSWORD, DB_PORT in your environment. const databasePool = new Pool({ host: process.env.DB_HOST || 'localhost', database: process.env.DB_NAME || 'bookshop_db', user: process.env.DB_USER || 'bookshop_app', password: process.env.DB_PASSWORD || '', port: parseInt(process.env.DB_PORT) || 5432, }); async function fetchAllBooks() { let client; try { // 'connect' borrows a connection from the pool client = await databasePool.connect(); const queryResult = await client.query( 'SELECT book_id, title, author_name, price FROM books ORDER BY price DESC' ); console.log(`Found ${queryResult.rowCount} books:\n`); // queryResult.rows is an array of plain JavaScript objects queryResult.rows.forEach(book => { console.log(` [${book.book_id}] ${book.title} by ${book.author_name} — $${book.price}`); }); } catch (queryError) { console.error('Database query failed:', queryError.message); } finally { // Return the connection back to the pool (don't destroy it) if (client) client.release(); await databasePool.end(); } } fetchAllBooks(); */
[3] Designing Data-Intensive Applications by Martin Kleppmann — $49.99
[1] The Pragmatic Programmer by David Thomas — $39.99
[2] Clean Code by Robert Martin — $34.50
Database connection closed cleanly.
| Aspect | psql (Command Line) | pgAdmin (GUI Tool) |
|---|---|---|
| Best for | Scripting, automation, server environments | Visual exploration, beginners learning table structure |
| Comes with PostgreSQL | Yes, always included | Yes, via EnterpriseDB installer (or install separately) |
| Speed for bulk operations | Very fast — no rendering overhead | Slower for large result sets due to UI rendering |
| Learning curve | Steeper — must know psql backslash commands | Gentler — click-through interface |
| Use in production | Yes — standard for DBAs and DevOps | Rarely — GUI tools aren't used on headless servers |
| Running SQL scripts | \i filename.sql — one command | Open file via Query Tool, then execute |
| Seeing table structure | \d tablename | Click the table in the left-side object tree |
🎯 Key Takeaways
- PostgreSQL runs as a background server on port 5432 — it must be started before any client (psql, pgAdmin, or your app) can connect to it.
- Always create a dedicated, limited-privilege database user for your application — never connect with the 'postgres' superuser in production code.
- In psql, every SQL statement must end with a semicolon. If you see ' ->' PostgreSQL is waiting for it — just type ';' and press Enter.
- Store database credentials in environment variables, never in source code — hardcoded passwords in Git repos are one of the most common real-world security failures.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using the 'postgres' superuser for your application — Symptom: app works fine locally, but you accidentally have full database admin rights in production, meaning a SQL injection bug could drop every table — Fix: always CREATE USER with a strong password and GRANT only the permissions that specific app needs. The postgres user is for admin tasks only.
- ✕Mistake 2: Forgetting the semicolon in psql — Symptom: you type a SQL command, press Enter, and see ' ->' instead of a result. psql just sits there waiting. Beginners think it froze — Fix: type a semicolon ';' followed by Enter. psql will immediately execute everything you typed. Every SQL statement in psql must end with ';'.
- ✕Mistake 3: PostgreSQL server not running when you try to connect — Symptom: you get 'could not connect to server: Connection refused — Is the server running on host localhost and accepting TCP/IP connections on port 5432?' — Fix: start the service first. On Linux: 'sudo systemctl start postgresql'. On macOS: 'brew services start postgresql@16'. On Windows: open Services (services.msc) and start 'postgresql-x64-16'.
Interview Questions on This Topic
- QWhat is the difference between the 'postgres' superuser and a regular PostgreSQL role, and why would you create a separate user for your application instead of using postgres?
- QWalk me through what happens step-by-step when your Node.js app calls pool.connect() to connect to a PostgreSQL database — what is a connection pool and why does it matter for performance?
- QIf your application throws 'FATAL: password authentication failed for user bookshop_app', what are the three most likely causes and how would you debug each one?
Frequently Asked Questions
What is the default port for PostgreSQL and can I change it?
PostgreSQL listens on port 5432 by default. You can change this by editing the 'port' setting in the postgresql.conf file (usually found at /etc/postgresql/16/main/postgresql.conf on Linux), then restarting the service. Changing the port is useful if you're running multiple PostgreSQL instances on the same machine.
What is the difference between a PostgreSQL database and a schema?
A database is the top-level container — you connect to a specific database. A schema is a namespace inside a database that groups tables together. By default, PostgreSQL puts everything in a schema called 'public'. Think of a database as a filing cabinet and a schema as a labelled drawer inside it. Most small projects only ever need the default public schema.
Do I need to install PostgreSQL locally if I'm using a cloud database like AWS RDS or Supabase?
You don't need a local PostgreSQL server to connect to a cloud database — the server lives in the cloud. However, you do need the psql client tools installed locally if you want to connect from your terminal. On macOS and Linux you can install just the client with 'brew install libpq' or 'sudo apt install postgresql-client' without installing the full server.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.