Create Your First Database
Create your first database and table — PostgreSQL Tutorial.
Focus: create your first database and table
You've installed PostgreSQL, connected to a server, and learned the essentials of SQL syntax. But every great application is built on data, and data needs a home. This lesson is where you stop exploring and start building: you'll create your first database, then create your first table inside it, and load it with a few rows. By the end, you'll have a solid foundation for everything that follows in this PostgreSQL Tutorial — from queries to performance tuning.
The problem this lesson solves
You can run SELECT 1; all day, but that doesn't help you store user profiles, orders, or sensor readings. The problem: a PostgreSQL server without databases is like an empty warehouse — it has the capacity, but no shelves to organize anything. To start building applications, you need a dedicated database to isolate your data from other projects, and then tables to define the structure of that data.
Databases ensure data isolation. If you're working on a personal blog and a side project for a client, you don't want their tables mixed together. A database is a logical container that holds all the objects (tables, indexes, views) for a specific application or use case. Within that database, tables define the columns (fields) and rows (records) that store your actual data. Without these, you can't run meaningful INSERT, SELECT, UPDATE, or DELETE statements.
This lesson bridges the gap between "I have PostgreSQL running" and "I can store real data." It's the first step toward building anything useful.
Core concept / mental model
Think of a PostgreSQL server as an apartment building. The server is the entire building, and each database is a separate apartment unit. The units are completely independent — you can decorate one without affecting your neighbor. Inside each apartment, the rooms are your tables. Each room has a purpose: the kitchen has counters (columns) for food prep, the bedroom has a bed and closet for storage. Tables define what kind of data you can put in them, and each row is a single record — like an inventory item in that room.
Key definitions:
- Database: A named collection of tables and other schema objects. Created with the
CREATE DATABASEcommand. - Table: A structured list of columns and constraints. Created with
CREATE TABLE. - Column: A named field with a specific data type (e.g.,
INTEGER,TEXT,DATE). - Row: A single record that fills each column with a value.
This mental model will help you as you move on to designing schema patterns and optimizing queries — the database is your high-level namespace, and tables are where the data lives.
How it works step by step
Creating a database and a table involves a few clear steps. Here's the logical sequence:
- Connect to the PostgreSQL server using
psqlor your preferred client. By default, you connect to a maintenance database calledpostgres. This is like entering the building's lobby — you can't store data here, but you can manage the apartments. - Check existing databases with
\lto see what's already there. This helps you avoid creating a duplicate. - Create your database with
CREATE DATABASE your_db_name;. This is like signing a lease for a new apartment. The command runs as the current user, and that user becomes the owner. - Connect to your new database — you can't create tables in a database you're not connected to. Use
\c your_db_nameto switch. - Design your table — decide what fields you need and the appropriate data types. For a simple
userstable, you might need an ID, name, email, and creation date. - Create the table with
CREATE TABLE. This defines the columns and constraints, likePRIMARY KEYorNOT NULL. - Verify the table using the
\dmeta-command to see its structure. - Insert a few rows (optional) to confirm everything works before you start building your application.
Let's walk through this with real commands in the next section.
Hands-on walkthrough
Open your terminal and connect to the PostgreSQL server. If you're on macOS, use psql postgres; on Linux/Windows, adjust the command as needed. We'll start by checking what databases exist:
$ psql -U your_username -d postgres
psql (16.x)
Type "help" for help.
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
(3 rows)
Now create your database. Let's call it my_blog — perfect for the rest of this tutorial:
CREATE DATABASE my_blog;
You should see CREATE DATABASE as confirmation. Now connect to it:
\c my_blog
Your prompt should change to my_blog=#. Now you're inside your new database. Next, create a table for blog posts. Here's a simple but realistic schema:
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
published_at TIMESTAMPTZ DEFAULT NOW()
);
Let's break down what each column does:
id—SERIALauto-increments, andPRIMARY KEYmakes it the unique identifier.title— aVARCHAR(200)string that can't be null.content—TEXTfor long-form body.published_at— a timestamp with time zone that defaults to the current time.
Verify the table structure:
\d posts
You'll see the column names, types, and constraints. Now insert a test row to make sure it all works:
INSERT INTO posts (title, content) VALUES ('Hello World', 'My first post!');
Then retrieve it:
SELECT * FROM posts;
Expected output:
id | title | content | published_at
----+------------+----------------+-------------------------------
1 | Hello World | My first post! | 2024-05-01 12:34:56.789+00
(1 row)
Pro Tip: Always end your SQL statements with a semicolon. If you forget, psql will keep waiting on the next line. Press
Ctrl+Cto cancel and retry.
Compare options / when to choose what
You might wonder whether to use SERIAL or something else for your primary key. PostgreSQL offers several options, and each has its trade-offs. Here's a quick comparison:
| Method | Description | Pros | Cons |
|---|---|---|---|
SERIAL |
Auto-incrementing integer | Simple, easy to read | May expose row count; not ideal for distributed systems |
GENERATED AS IDENTITY |
Standard SQL way | More compliant, can set BY DEFAULT or ALWAYS |
Slightly more verbose |
UUID |
Random unique identifier | No gap guessing, great for merging data | Larger storage, not human-friendly |
For most tutorials and small applications, SERIAL is perfectly fine. If you're building a public-facing API where you don't want to reveal how many rows you have, consider using UUID. If you need strict SQL standards compliance, use GENERATED AS IDENTITY.
Similarly, when choosing between TEXT and VARCHAR(n): TEXT has no length limit and is generally recommended unless you have a business rule that limits field length (like a username of max 20 chars). Both store variable-length strings, but VARCHAR(n) enforces the limit.
Troubleshooting & edge cases
You'll run into a few common issues. Here's how to fix them:
- "Database \"my_blog\" already exists" — You tried to create a duplicate. Use
\lto check existing databases, and choose a new name or useDROP DATABASE if exists(but be careful — that deletes everything). - "ERROR: permission denied to create database" — Your user isn't allowed to create databases. This often happens on managed PostgreSQL (like RDS). You need to be a superuser or have the
CREATEDBprivilege. RunALTER USER your_username CREATEDB;as a superuser. - "ERROR: relation \"posts\" already exists" — The table exists. If you're just experimenting, you can
DROP TABLE posts;and recreate, but in production always useDROP TABLE IF EXISTSwith caution. - Your psql prompt doesn't change after
\c my_blog— Make sure you typed the command without a semicolon. Meta-commands like\cdon't need semicolons. - But wait, what about connecting from an application? — When your code connects, you'll need to specify the database name in the connection string. For example, in Python with psycopg2:
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="my_blog",
user="your_username",
password="your_password"
)
Now your app knows exactly which database to use.
What you learned & what's next
You've accomplished a lot: you created your first database (my_blog), created a posts table with proper column types, inserted a row, and retrieved it. You now understand the difference between a database and a table, and you're comfortable with the basic workflow every developer performs daily. This is the foundation for everything else in PostgreSQL — you can't query data you haven't stored.
In the next lesson, you'll learn how to modify your data with INSERT, UPDATE, and DELETE statements. You'll also explore how to query with filters and sorting. But for now, practice creating another table — maybe with your own schema — and insert a few rows. The more you play with CREATE TABLE, the more natural it becomes.
Keep this momentum going: you're on your way to mastering PostgreSQL, one table at a time.
Practice recap
Create a new database called test_practice, then design and create a table named products with columns id, name, price, and in_stock. Insert three sample rows and query them back. Try breaking things on purpose — duplicate the table name, omit a semicolon — to see the error messages and get comfortable with troubleshooting.
Common mistakes
- Using
CREATE DATABASEwithout checking if it exists, causing an error — always list databases with\lfirst or useCREATE DATABASE IF NOT EXISTS(though note PostgreSQL doesn't support that clause; you need to check manually). - Forgetting to connect to the new database before creating tables — you'll end up creating tables in the
postgresdatabase by accident. - Using
VARCHAR(255)habitually whenTEXTwould be more flexible and performs identically in PostgreSQL. - Missing the semicolon at the end of
CREATE TABLE—psqlwill wait for more input, causing confusion.
Variations
- Use
GENERATED ALWAYS AS IDENTITYinstead ofSERIALfor standard-compliant auto-incrementing primary keys. - Create the database using the
createdbcommand-line utility instead of the SQLCREATE DATABASE— useful in scripts. - Define the schema with
CREATE SCHEMA myapp;and and place tables inside it for multi-tenant applications.
Real-world use cases
- A new web app needs a dedicated database and a
userstable to store sign-up data. - A data analytics pipeline creates a database per client project, each with
eventsandmetricstables. - A migration script builds a fresh database and tables during CI/CD to test schema changes.
Key takeaways
- A database is a logical container; a table is a structured list of columns and rows.
- Use
CREATE DATABASEandCREATE TABLEwith explicit data types and constraints. - Always connect to the target database before creating tables.
SERIALprimary keys are easy to use;GENERATED AS IDENTITYis more standards-compliant.- Check your work with
\dand test with anINSERTandSELECT. - Troubleshoot permission errors by checking your user's role privileges.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.