Create Your First Database

Create your first database and table — PostgreSQL Tutorial.

Focus: create your first database and table

Sponsored

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 DATABASE command.
  • 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:

  1. Connect to the PostgreSQL server using psql or your preferred client. By default, you connect to a maintenance database called postgres. This is like entering the building's lobby — you can't store data here, but you can manage the apartments.
  2. Check existing databases with \l to see what's already there. This helps you avoid creating a duplicate.
  3. 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.
  4. Connect to your new database — you can't create tables in a database you're not connected to. Use \c your_db_name to switch.
  5. Design your table — decide what fields you need and the appropriate data types. For a simple users table, you might need an ID, name, email, and creation date.
  6. Create the table with CREATE TABLE. This defines the columns and constraints, like PRIMARY KEY or NOT NULL.
  7. Verify the table using the \d meta-command to see its structure.
  8. 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:

  • idSERIAL auto-increments, and PRIMARY KEY makes it the unique identifier.
  • title — a VARCHAR(200) string that can't be null.
  • contentTEXT for 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+C to 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 \l to check existing databases, and choose a new name or use DROP 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 CREATEDB privilege. Run ALTER 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 use DROP TABLE IF EXISTS with caution.
  • Your psql prompt doesn't change after \c my_blog — Make sure you typed the command without a semicolon. Meta-commands like \c don'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 DATABASE without checking if it exists, causing an error — always list databases with \l first or use CREATE 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 postgres database by accident.
  • Using VARCHAR(255) habitually when TEXT would be more flexible and performs identically in PostgreSQL.
  • Missing the semicolon at the end of CREATE TABLEpsql will wait for more input, causing confusion.

Variations

  1. Use GENERATED ALWAYS AS IDENTITY instead of SERIAL for standard-compliant auto-incrementing primary keys.
  2. Create the database using the createdb command-line utility instead of the SQL CREATE DATABASE — useful in scripts.
  3. 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 users table to store sign-up data.
  • A data analytics pipeline creates a database per client project, each with events and metrics tables.
  • 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 DATABASE and CREATE TABLE with explicit data types and constraints.
  • Always connect to the target database before creating tables.
  • SERIAL primary keys are easy to use; GENERATED AS IDENTITY is more standards-compliant.
  • Check your work with \d and test with an INSERT and SELECT.
  • Troubleshoot permission errors by checking your user's role privileges.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.