A field of small green cells labelled SQLite, each holding a database, facing one large blue database tower labelled Postgres

Engineering · · 5 min read · Giovanni Liber

SQLite vs Postgres: which one does your app need?

When a small database per project is the better design, and when you want a Postgres server

The short answer

Choose SQLite when one app owns its data and that data fits on one server. Choose Postgres when many writers or many services share one dataset, or when you need its extensions and types.

They aren’t rivals for the same job. In SQLite’s own words, it “does not compete with client/server databases”.

We have a stake in this: Eurobase offers SQLite databases for Workers and doesn’t offer managed Postgres. So this article also says where our databases are the wrong choice.

Decide in 30 seconds

Your appPick
One app owns its dataSQLite
Each project keeps its data apartSQLite
Mostly reads, with moderate writesSQLite
A coding agent builds and deploys the appSQLite
Many writers on the same data, all the timePostgres
Several services share one databasePostgres
One dataset of hundreds of gigabytes or morePostgres
Extensions such as PostGIS or pgvectorPostgres
Arrays, ranges or timestamps with time zonesPostgres
An app that already runs on PostgresPostgres

If your answers point both ways, the Postgres rows usually decide: they’re the hard ones to work around. On Eurobase, a database stops at 2 GB on paid plans, so a larger dataset needs a home elsewhere.

Where each one runs

Postgres is a server. Your app usually connects to it over the network, and it starts a new process for each connection. The default limit is typically 100 connections. That’s why many apps put a connection pool in front of it.

SQLite is a library that reads and writes a file. It has no server process and nothing to connect to.

Eurobase changes one thing. Each database gets a small process of its own on the server that runs your app, and your app reaches it through a local socket. Your app never touches the file, and the query never crosses a network.

SQLite on Eurobase
Your appLocal socketYour database
One server, and nothing else on the path.
A typical Postgres setup
Your appsNetworkConnection poolNetworkPostgres server
A server of its own, shared by every app that connects.

When SQLite is the better choice

Each project keeps its data apart
Every database has its own file, process and memory limit. A slow query stalls only that database, and there’s no shared server to size. On Eurobase, a database belongs to one project, which can have up to 3 on Free and 10 on paid plans. For a database per customer, deploy a project per customer.
No connection pool
Your code calls env.DB. There’s no connection string to store, no password to rotate and no pool to tune.
Queries run next to the app
With no network in the path, a query has no network delay. On a test server with 3 vCPUs and 4 GB of memory, 100 databases took 1,936 calls a second in total, from clients on that server, with no Worker in the path. p99 was 4 ms for reads and 6 ms for writes, and the slowest write took 150 ms.
Backups you don’t run
Every committed change is copied continuously to object storage on European infrastructure, where each copy is locked against deletion. There’s no backup job to set up or check.
Small and medium apps
SQLite says it works for most low to medium traffic websites, and calls 100,000 hits a day a conservative estimate, not an upper bound. In WAL mode, readers don’t block the writer, and the writer doesn’t block readers. On Eurobase, each database runs one call at a time, so a long query delays that database’s other calls.
Apps built by coding agents
The database’s setup lives in the repository: a few lines of config and SQL files. An agent needs no second account and no database credential, and it can run the same migrations locally before it pushes.

When Postgres is the better choice

Many writers on one dataset
SQLite allows one writer at a time, per database. In Postgres, row locks block only writers of the same row, so transactions on different rows run side by side. If your app writes all day from many clients to the same tables, this is the reason to choose Postgres.
Very large datasets
The SQLite format reaches 281 TB, far beyond the size of a Eurobase database. Postgres has no limit on database size and allows 32 TB per table, and it can add standby servers that answer read-only queries.
Extensions
CREATE EXTENSION adds features to the database itself: PostGIS for geospatial data, pgvector for vector search.
Rich types
SQLite has five storage classes and no date or boolean type. Postgres has many built-in types, including jsonb, uuid, timestamptz, arrays and ranges. Its ALTER TABLE also does far more than SQLite’s limited subset.
Many services on one database
When several services reach the same data over a network, SQLite itself says to use a client/server database. Postgres can also give each service its own role, and row security policies limit what each one sees. On Eurobase, apps in other projects can’t reach your project’s database.
Recovery in your own hands
With Postgres you can run pg_dump at any time, and, with WAL archiving set up in advance, restore to a point in time. On Eurobase, you can’t export a database or restore it to an earlier point yourself.
An app that already runs on Postgres
Types, SQL and drivers all differ, so moving to SQLite means changing your data layer. If your app works, keep Postgres.

What to know about SQLite on Eurobase

  • Databases work with Workers. A project that runs as a container can’t use them.
  • You can’t use BEGIN in your SQL. To run several statements in one transaction, send them with env.DB.batch(). Your code can’t hold a transaction open between statements.
  • A write is confirmed once it’s committed on the server, and its upload starts at the same moment. If a server crashes or is lost, the confirmed writes not yet uploaded are lost with it: usually the last fraction of a second, more while the backup is behind.

If you need Postgres

You can host your app on Eurobase and keep your database with a Postgres provider. A container can connect with any Postgres driver to a host with an IPv4 address. A Worker can’t open a Postgres connection, so it needs the provider’s HTTP API. Eurobase + Supabase shows both.

Add SQLite to a Worker on Eurobase

Declare the database in your Wrangler config:

// wrangler.jsonc
{
  "d1_databases": [
    { "binding": "DB", "database_name": "shop" }
  ]
}

Add a migration:

-- migrations/0001_init.sql
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL);

Query it through env.DB with the D1 API:

// src/index.ts
export default {
  async fetch(request, env) {
    const { results } = await env.DB
      .prepare('SELECT id, name FROM products WHERE price < ?')
      .bind(20)
      .all();
    return Response.json(results);
  }
};

Push. Eurobase creates the database with the next deployment and runs new migrations before that deployment goes live. If a migration fails, the deployment fails and the previous one keeps serving. Migrations that already ran stay applied, and a rollback doesn’t undo them. Variables and bindings has the details.

Limits

LimitFreePaid plans
Databases per project310
Size of a database100 MB2 GB
Rows read per day5,000,000No daily limit
Rows written per day100,000No daily limit
Data written per day400 MiB2 GiB
Time per query or batch30 s30 s
Backup history7 days30 days

Daily limits apply to each database and reset at 00:00 UTC. Over a row limit, queries fail until the reset. Over the data limit, reads keep working and writes fail. Data written counts changed database pages, at least one per write, so small writes use it faster than their size suggests. A deployment that would add more databases than your plan allows is refused.

Deploy your app in Europe

Sign up