Imagine you want to build a simple web app — maybe a contact form, a blog, or a small API. You search for "PHP framework" and find Laravel or Symfony. You read the docs, and suddenly you're looking at artisan commands, service providers, middleware pipelines, and a dozen folders to learn before you write a single line of code.
What if there was another way? A framework that lets you write Hello, world in 5 lines — not 5 files?
That's Fat Free Framework. And this blog series will teach you how to use it.
What is Fat Free Framework?
Fat Free Framework (F3) is a PHP micro-framework that weighs in at just ~90 kilobytes. It's small enough to fit in your pocket, but has every tool you actually need to build real web applications.
Think of it like a Swiss Army knife. A full-size kitchen knife (Laravel) is great for professional chefs building large restaurants. But if you just need to slice an apple, the Swiss Army knife gets the job done — and you can carry it anywhere. F3's smaller footprint also means cheaper hosting: shared hosting with limited resources can run F3 apps that would choke on Laravel's memory requirements.
base.php. No service container, no dependency injection container, no complex bootstrapping.Instead, F3 uses a registry pattern — a simple key-value store accessed via
$f3->set() and $f3->get(). For senior engineers: this trades the type-safety and testability of DI for simplicity. F3's registry works, but you lose compile-time checks and the ability to easily mock dependencies in tests. Current version: 3.9.x (PHP 8.0+).What Can You Build with F3?
F3 isn't just for "Hello World" demos. It ships with a full toolkit that handles real work:
| Feature | What It Does | Example Use |
|---|---|---|
| Routing Engine | Maps URLs to code | /blog/my-post shows that post |
| Template Engine | Renders HTML with logic | Dynamic pages, email templates |
| SQL Mapper | Talks to databases without raw SQL | User accounts, blog posts |
| Cache Engine | Speeds up repeated requests | Page cache, API responses |
| i18n | Multilingual support | Spanish, French, Japanese sites |
| Audit & Security | DNSBL spam blocking, input validation | Contact forms, login pages |
People have built: REST APIs, content management systems, e-commerce sites, URL shorteners, image galleries, and internal business tools.
Why Choose a Micro-framework?
A micro-framework gives you the essentials and gets out of your way. Here's what that means in practice:
- Fast to learn — You can be productive in hours, not weeks. No need to memorize dozens of framework-specific CLI commands or config file formats.
- Fast to install — One
composer requireand you're done. Nophp artisan key:generate, no .env setup, no config files. - Fast to run — ~90kb loads instantly. For context: framework boot time (~50-100ms for Laravel) is rarely the bottleneck in real apps. F3's advantage is a simpler mental model and less memory overhead, not measurably faster TTFB.
- No boilerplate — Write only the code your app needs. Delete what you don't use.
- Flexible — Use native PHP, Twig, Smarty, or F3's own template engine. Your choice.
For senior engineers: F3 has community plugins, but a smaller ecosystem than Laravel's Packagist packages. If your project depends on specific Laravel-only packages (Socialite, Horizon, Nova), factor that into your decision. F3 is best for small-to-medium projects, APIs, rapid prototyping, and teams who prefer freedom over conventions.
| Framework | Core Size | Learning Curve | Best For |
|---|---|---|---|
| Fat Free | ~90kb | Easy | Small apps, APIs, prototyping |
| Slim | ~200kb | Easy | APIs, microservices |
| CodeIgniter 4 | ~1.5MB | Easy | Traditional MVC, legacy migration |
| Laravel | ~10MB | Moderate | Full-stack apps, teams |
| Symfony | ~30MB | Steep | Enterprise systems |
Installation
F3 has exactly one requirement: PHP 8.0 or later (for version 3.9). No Apache modules required. No vendor lock-in.
Note: F3 doesn't bundle database drivers — you'll still need PHP extensions like pdo_mysql or pdo_sqlite installed if your app uses a database. Check with php -m | grep pdo.
php -vF3 3.9 requires PHP 8.0+. If you're using F3 3.8, PHP 7.2+ works. Check the system requirements for your version.
Option A: Composer (Recommended)
Composer is PHP's package manager. If you don't have it, install it first.
# Create your project folder
mkdir my-app && cd my-app
# Install F3
composer require bcosca/fatfree-core This creates a vendor/ folder and sets up autoloading automatically. When you need F3, just require the autoloader:
require 'vendor/autoload.php'; Base class to vendor/bcosca/fatfree-core/base.php. When you call \Base::instance(), PHP checks Composer's autoload registry, loads the file, and returns the singleton. No manual require statements needed.Option B: Manual Download (No Composer)
If you prefer not to use Composer, download the framework directly:
- Go to github.com/bcosca/fatfree-core
- Click the green "Code" button → "Download ZIP"
- Extract the ZIP and find
base.php - Copy
base.phpinto your project folder
Hello World in 5 Lines
Create a file called index.php and paste this:
<?php
$f3 = \Base::instance();
$f3->route('GET /', function() {
echo 'Hello, world!';
});
$f3->run(); \Base::instance() — Creates (or returns) the single F3 object. This is where all routes, config, and data live.Line 3-5:
$f3->route(...) — Tells F3: "When someone visits / with a GET request, run this function."Line 7:
$f3->run() — Starts F3. It looks at the current URL, finds the matching route, runs the function, and sends the response.$f3->route(), F3 doesn't run anything yet. It stores the route in an internal array called $f3->routes. Each route has three parts: HTTP method, URL pattern, and callback.When
$f3->run() is called, F3 reads $_SERVER['REQUEST_METHOD'] and $_SERVER['REQUEST_URI'], then iterates through $f3->routes to find the first match. The matched callback receives the F3 instance and any captured URL parameters.index.php2. Run the built-in PHP server:
php -S localhost:80003. Open your browser to
http://localhost:8000Expected output: A blank page with the text "Hello, world!"
How a Request Flows Through F3
When you visit a URL, here's what happens inside F3:
Your Browser
│
▼
┌─────────────────┐
│ HTTP Request │ GET http://localhost:8000/
└────────┬────────┘
│
▼
┌─────────────────┐
│ index.php │ PHP script starts
└────────┬────────┘
│
▼
┌─────────────────┐
│ Base::instance() │ Create F3 singleton
└────────┬────────┘
│
▼
┌─────────────────┐
│ $f3->route() │ Register route (store in array)
└────────┬────────┘
│
▼
┌─────────────────┐
│ $f3->run() │ Start the framework
└────────┬────────┘
│
▼
┌─────────────────┐
│ Match route? │ GET / → Yes, match found
└────────┬────────┘
│
▼
┌─────────────────┐
│ Run callback │ echo "Hello, world!"
└────────┬────────┘
│
▼
┌─────────────────┐
│ Send response │ HTTP 200 + "Hello, world!"
└────────┬────────┘
│
▼
Your Browser
sees the page Project Structure: Start Simple, Grow Later
For now, one index.php file is all you need. But as your app grows, here's a structure that works well:
my-app/
├── index.php # Entry point - all routes here
├── vendor/ # Composer packages
├── src/
│ ├── controllers/ # Route handlers (later)
│ └── models/ # Database classes (later)
├── views/ # HTML templates (later)
└── public/ # CSS, JS, images Don't create all these folders now. Start with index.php. When complexity grows — multiple route handlers, shared logic, or the file becomes hard to navigate — split routes into a controllers/ folder. For experienced devs: refactor based on complexity and coupling, not just line count.
System Requirements
F3 is intentionally minimal. Here's everything you need:
| Requirement | Minimum | Recommended |
|---|---|---|
| PHP Version | 8.0 | 8.3 |
| Web Server | None (built-in works) | Apache, Nginx, Caddy |
| Database | None (optional) | MySQL, SQLite, PostgreSQL, MongoDB |
| Extensions | None | pdo_mysql, pdo_sqlite, or pdo_pgsql (depending on database) |
You can run F3 with PHP's built-in development server — no Apache or Nginx needed for development:
# Run on port 8000 (default)
php -S localhost:8000
# Run on a different port
php -S localhost:3000
# Run on all interfaces (development only!)
php -S 0.0.0.0:8000 Troubleshooting
Running into issues? Here are the most common problems:
| Error | Cause | Fix |
|---|---|---|
| "php" is not recognized | PHP not installed or not in PATH | Install PHP or use full path: /usr/bin/php |
| Port already in use | Another service using that port | Use a different port: php -S localhost:3000 |
| Blank page | PHP error is hidden | Add ini_set('display_errors', 1); at top of index.php |
| 404 Not Found | Route doesn't match URL | Check your route definition matches the URL exactly |
| Composer command not found | Composer not installed | Install from getcomposer.org |
| Permission denied (port 80) | Ports below 1024 need root | Use a higher port (8000) for dev. For production, use Nginx as reverse proxy. |
Add this at the top of your
index.php during development:ini_set('display_errors', 1); error_reporting(E_ALL);This shows all PHP errors in the browser instead of a blank page. For production, log errors to a file instead.
What's Next?
You now have F3 running. In Part 2: Routing & Controllers, we'll go deeper:
- Route parameters —
/user/42and capturing values - HTTP methods — GET, POST, PUT, DELETE
- Route groups — shared prefixes and filters
- Controllers — organizing code into classes
- Before/after filters — authentication, logging
The best way to learn is to do. You've already built your first F3 app. Now let's build something real.