SQLite or PostgreSQL: which database to choose

How SQLite and PostgreSQL differ: fourteen criteria, which database for which project, signs that SQLite is no longer enough, portable SQL, the move step by step and common mistakes.

Stack and technologies Updated

In short

SQLite is a database in one file next to the application: nothing to install or administer, very fast reads, and enough for most sites, blogs, catalogues and internal tools on one server. PostgreSQL is a separate database server: many simultaneous writers, access over the network from several servers, users and rights, strict types and extensions such as PostGIS and pgvector. Choose SQLite when the project lives on one server and writes are moderate; choose PostgreSQL when there are many writers, several servers or the data needs its own rules. Starting on SQLite and moving later is normal if the SQL is portable from day one.

In short: which one to choose

The question is not which database is better but where the data lives and who writes to it. If the application runs on one server and most requests read — pages, a catalogue, articles, settings — SQLite gives the same result with less to run: no server, no connections, a backup is a copy of a file.

PostgreSQL is needed when the file stops being enough: many users write at the same moment, the application runs on several servers, analysts connect over the network, different people need different rights, or the project relies on extensions — maps, vector search, time series.

  • One server, mostly reads — SQLite
  • Many writers or servers — PostgreSQL
  • Not sure — SQLite with portable SQL

SQLite and PostgreSQL: a detailed comparison

Fourteen criteria side by side — from how the data is stored to backups and hosting.

CriterionSQLitePostgreSQL
Where the data lives one file next to the code a separate database server
Setup none — a library in the language a server, users, settings
Reads very fast, no network fast, through a connection
Simultaneous writes one writer at a time many, locks on rows
Several app servers no, one machine yes, over the network
Users and rights file permissions only roles, rights down to rows
Types lax by default, strict with STRICT strict always
Changing the schema limited ALTER TABLE full, inside a transaction
JSON functions and the ->> operator jsonb with indexes
Full-text search FTS5, no morphology built in, with language dictionaries
Extensions a few PostGIS, pgvector, TimescaleDB and hundreds more
Replication external tools: Litestream, LiteFS built in
Backups a copy of a file via .backup pg_dump, restoring to any moment
Hosting anywhere the code runs VPS or managed service

Which database for which project

Twelve typical projects with a recommendation and the reason.

ProjectTakeWhy
Corporate site or blog SQLite almost only reads
Catalogue or reference site SQLite fast reads, a backup is one file
Internal tool for a team SQLite few writers, nothing to administer
Prototype or MVP SQLite start today, move when it grows
Online shop with orders PostgreSQL orders and stock change at the same moment
SaaS with many clients PostgreSQL many writers, rights down to rows
CRM or accounting PostgreSQL strict types and constant writes
App on several servers PostgreSQL one file cannot be shared over the network
Maps and geodata PostgreSQL PostGIS
Search by meaning, RAG PostgreSQL pgvector next to the data
Browser extension or desktop app SQLite a database inside the app, no server
Analytics over the network PostgreSQL analysts connect with their own tools

Signs that SQLite is no longer enough

SQLite rarely fails suddenly — it sends signals. Any one of them is a reason to plan the move.

  1. 01

    “database is locked” in the logs

    It keeps appearing even with WAL and busy_timeout — writers queue up for too long.

  2. 02

    A second server

    The application needs to run on two machines, and both must write.

  3. 03

    Access over the network

    Analysts, reports or another service need to read the data directly.

  4. 04

    Different rights

    Someone must see only their own rows, and the code alone should not be the only guard.

  5. 05

    An extension is needed

    Maps, vector search or time series — PostGIS, pgvector, TimescaleDB.

  6. 06

    Search with morphology

    Users search by word forms, and FTS5 finds only exact ones.

The difference in SQL: 3 examples

What works the same in both, where they differ, and how to move. Every example was run in SQLite 3.40 and PostgreSQL 16.

Portable SQL

A table and an upsert with RETURNING that run unchanged in both — the basis for an easy move.

upsert.sql
-- One file, two databases: runs unchanged in SQLite 3.35+ and PostgreSQL
CREATE TABLE products (
    sku   TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    price INTEGER NOT NULL CHECK (price >= 0),
    stock INTEGER NOT NULL DEFAULT 0
);

-- Add a product or, if the SKU exists, update the price and add to the stock
INSERT INTO products (sku, title, price, stock)
VALUES ('A-100', 'Oak table', 24000, 3)
ON CONFLICT (sku) DO UPDATE
    SET price = excluded.price,
        stock = products.stock + excluded.stock
RETURNING sku, price, stock;
-- first run:  A-100 | 24000 | 3
-- second run: A-100 | 24000 | 6

Types: the main difference

One INSERT, three results. An ordinary SQLite table keeps text in a number column; STRICT and PostgreSQL refuse.

types.sql
-- The same INSERT with a word instead of a number
INSERT INTO products (sku, title, price)
VALUES ('C-300', 'Birch chair', 'free');

-- SQLite, ordinary table: the row is saved, price = 'free'
--   (CHECK passes too: in SQLite any text is "greater" than any number)
-- SQLite, table declared STRICT:
--   cannot store TEXT value in INTEGER column products.price
-- PostgreSQL:
--   invalid input syntax for type integer: "free"

Moving a table

Export to CSV, load with \copy and move the id counter — the step most often forgotten.

move.sh
# Move a table from SQLite to PostgreSQL. The table in PostgreSQL is created first:
#   CREATE TABLE products (id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
#                          sku text UNIQUE NOT NULL, title text NOT NULL, price integer NOT NULL);

# 1. Export from SQLite to CSV
sqlite3 -header -csv app.db "SELECT id, sku, title, price FROM products" > products.csv

# 2. Load into PostgreSQL, keeping the same ids
psql "$DATABASE_URL" -c "\copy products (id, sku, title, price) FROM 'products.csv' CSV HEADER"

# 3. Move the id counter past the loaded rows,
#    or the next INSERT fails with "duplicate key value violates unique constraint"
psql "$DATABASE_URL" -c "SELECT setval(pg_get_serial_sequence('products', 'id'), max(id)) FROM products"

Moving from SQLite to PostgreSQL: what to keep in mind

The data moves easily; the surprises come from what SQLite forgave and PostgreSQL does not.

  1. 01

    Check the types first

    Find text in number columns with typeof() and clean it before the move, or the load stops on the first bad row.

  2. 02

    pgloader for large databases

    It reads the SQLite file directly and moves the schema and data in one command.

  3. 03

    Id counters

    After loading with explicit ids, move each counter with setval.

  4. 04

    Booleans and dates

    SQLite keeps them as 0/1 and text — in PostgreSQL they become boolean and timestamptz.

  5. 05

    LIKE and case

    In SQLite LIKE ignores case for Latin letters, in PostgreSQL it does not — searches need ILIKE.

  6. 06

    Double quotes

    SQLite often accepts a string in double quotes; PostgreSQL reads it as a column name and fails.

Common mistakes when choosing between them

  1. PostgreSQL “just in case”

    A separate server, updates and backups for a site that only reads.

  2. SQLite on a network drive

    Locks over the network are unreliable — the file gets damaged.

  3. Blaming SQLite without WAL

    Most “SQLite is slow” complaints disappear after WAL and busy_timeout.

  4. Lax types for years

    Without STRICT, dirty data accumulates and surfaces on the day of the move.

  5. SQL tied to one database

    Specific functions everywhere turn a simple move into a rewrite.

  6. Moving because of the name

    If the slowness is in queries and indexes, PostgreSQL will be slow too.

Questions about SQLite and PostgreSQL

Is SQLite suitable for production?

Yes, for a project on one server with moderate writes — with WAL, busy_timeout and regular backups.

Which is faster?

SQLite on reads in one application — there is no network; PostgreSQL when many write at the same time.

How much traffic can SQLite handle?

Reads are rarely the limit; the limit is how many writes arrive at the same moment.

Can I start on SQLite and move later?

Yes, if the SQL is portable and the tables are STRICT — then the move takes hours, not weeks.

Does SQLite support JSON?

Yes, functions and the ->> operator; PostgreSQL adds jsonb with indexes on fields.

How do I back up each one?

SQLite — .backup or Litestream, not a plain copy of a live file; PostgreSQL — pg_dump and WAL archiving.

What about MySQL?

It is a server database like PostgreSQL; how they differ is in a separate comparison.

Online form

Choosing
the database

I work with both: SQLite for projects on one server, PostgreSQL when the load, the team or the data need more. Tell me about the project — I answer within one working day.

Or write to [email protected]