Web-dev

PHP + SQLite — The Underrated Stack for Small Projects

Published 2026-07-22 ~162 words Tags: php, sqlite, backend, hosting

Everyone reaches for Node.js + MongoDB or Python + PostgreSQL for every project these days. But for the vast majority of small-to-medium web apps, PHP + SQLite is a better fit — and it's not even close.

Why This Combo Wins

Zero setup. No database server. No daemon. No Docker. You upload files to shared hosting and it works. PHP has SQLite built in since PHP 5.4. There's nothing to install.

No background processes. SQLite is a file. Backups are cp data.db backup.db. No pg_dump, no mysqldump, no running services to restart.

Fast enough. SQLite handles millions of rows per table. For a site with thousands of articles or users, the bottleneck is always your PHP code, not the database. A simple query on a SQLite file with an index runs in microseconds.

IONOS shared hosting? Works perfectly. The same PHP that runs WordPress runs SQLite just as well. You don't need a VPS.

Setting It Up

The pattern is dead simple:

<?php
$db = new PDO('sqlite:' . __DIR__ . '/data/site.sqlite3');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('PRAGMA journal_mode=WAL');
$db->exec('PRAGMA foreign_keys=ON');

That's it. You have a database. The WAL mode is critical on shared hosting — it allows concurrent reads while writing, avoiding the "database is locked" errors.

Full-Text Search Without Elasticsearch

SQLite 3.9+ has FTS5 — a full-text search engine built in. It's surprisingly capable:

$db->exec("
    CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5(
        title, body, content='articles', content_rowid='id',
        tokenize='porter unicode61'
    )
");

Now you can run:

SELECT * FROM articles_fts WHERE articles_fts MATCH 'search terms'

This handles stemming, ranking, and Boolean queries. For a tutorial site or blog, you don't need Elasticsearch. You don't even need MySQL.

The Router Pattern

A single index.php handles everything. No framework, no routing library:

<?php
$action = $_GET['action'] ?? 'home';
switch ($action) {
    case 'home':
        // render article list
        break;
    case 'article':
        $slug = preg_replace('/[^a-z0-9-]/', '', $_GET['slug']);
        // render single article
        break;
}

With a .htaccess to rewrite clean URLs:

RewriteRule ^article/([a-z0-9-]+)$ index.php?action=article&slug=$1 [L,QSA]
RewriteRule ^$ index.php?action=home [L,QSA]

File-Based Content

Store your content as Markdown files, not in the database. Metadata in SQLite, body in .md files. Best of both worlds:

  • Edit articles in any text editor
  • Git-track them naturally
  • Database stays lean — just metadata + FTS indexes
  • Easy bulk import/export

When NOT to Use SQLite

Honestly, the list is short:

  • Multiple concurrent writers — If you have 50 people inserting at the same time, use PostgreSQL.
  • Hundreds of millions of rows — SQLite handles this, but performance degrades. At that scale, you can afford a server.
  • Horizontal scaling — SQLite is single-file. If you need read replicas, use something else.

For everything else — personal projects, small SaaS, tutorials, internal tools, landing pages — PHP + SQLite is the fastest path to shipping.