Slim 4: A fast, modern micro-framework (with a tiny JSON stats endpoint)
Like the size of Fat-Free Framework (F3) or Flight, but want something that feels more “2025”? Slim 4 is a small, fast PHP micro-framework that gives you clean routing and middleware without dragging in a giant ecosystem. It plays nicely with Composer, PSR standards, and all your existing plain PHP pages.
Think of Slim as a tiny front controller + router you can bolt in front of your site – perfect for JSON endpoints, mini dashboards, and one-off tools. In this post we’ll:
- Show how Slim 4 feels like a modern F3/Flight-style micro-framework.
- Set up a minimal Slim 4 app.
- Add a JSON stats endpoint that reads from a simple MySQL table.
1. Why Slim 4 feels like a modern F3 / Flight
If you’ve ever used F3 or Flight:
- Small surface area – you mostly touch routes, middleware, and a container.
- Explicit routing – define
GET/POSTroutes with named parameters. - Modern PSR stack – PSR-7 (HTTP messages), PSR-15 (middleware), PSR-11 (container).
- Composer-friendly – per-project install, no system-wide magic.
- Easy to mix with old PHP – you can still
includeexisting files and libraries.
For webmasters, that means you can keep your site mostly “normal PHP”, and drop Slim in for the parts that benefit from a proper router or JSON API.
2. Install Slim 4 with Composer
Inside a new folder (or a subfolder on your site), run:
composer require slim/slim:"^4.0" slim/psr7
This pulls in Slim 4 plus a PSR-7 implementation so you can work with $request and $response objects.
3. Minimal Slim 4 front controller
Create public/index.php (or just index.php if you’re dropping Slim directly into a folder that’s already web-accessible):
<?php // public/index.php (or /slim/index.php) // 1) Composer autoload require __DIR__ . '/../vendor/autoload.php'; use Slim\Factory\AppFactory; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; // 2) Create the app $app = AppFactory::create(); // Optional: set base path if app is not in web root. // $app->setBasePath('/slim'); // 3) Simple homepage route $app->get('/', function (Request $request, Response $response): Response { $response->getBody()->write("Hello from Slim 4 👋"); return $response; }); // 4) Simple JSON ping route $app->get('/api/ping', function (Request $request, Response $response): Response { $data = [ 'status' => 'ok', 'time' => date('c'), ]; $payload = json_encode($data, JSON_PRETTY_PRINT); $response->getBody()->write($payload); return $response->withHeader('Content-Type', 'application/json'); }); // 5) Run the app $app->run(); Visit the folder in your browser (or via php -S localhost:8000 -t public) and you should see:
/→ “Hello from Slim 4 👋”/api/ping→ a tiny JSON response
Apache .htaccess example
If you’re on Apache, drop this .htaccess next to your index.php so all non-file/non-directory requests go through Slim:
<IfModule mod_rewrite.c> RewriteEngine On # If the requested file or folder exists, serve it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # Otherwise, send everything to index.php RewriteRule ^ index.php [QSA,L] </IfModule> 4. Add a tiny JSON stats endpoint (MySQL)
Now let’s do something a little more “webmaster”: a JSON endpoint that returns your last 7 days of stats from a simple table.
Example table (you can adapt this to your own stats schema):
CREATE TABLE stats_daily ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, day DATE NOT NULL, visits INT UNSIGNED NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; We’ll wire Slim to read from this table with PDO and return JSON like:
[ { "day": "2025-01-01", "visits": 123 }, { "day": "2025-01-02", "visits": 98 }, ... ] Update your index.php to add a PDO connection and one more route:
<?php require __DIR__ . '/../vendor/autoload.php'; use Slim\Factory\AppFactory; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface; // ── 1) Basic DB config (adjust for your server) ─────────── $DB_HOST = 'localhost'; $DB_NAME = 'your_database'; $DB_USER = 'your_user'; $DB_PASS = 'your_pass'; $dsn = 'mysql:host=' . $DB_HOST . ';dbname=' . $DB_NAME . ';charset=utf8mb4'; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; $pdo = new PDO($dsn, $DB_USER, $DB_PASS, $options); // ── 2) Create Slim app ──────────────────────────────────── $app = AppFactory::create(); // Optional base path if app is in subfolder // $app->setBasePath('/slim'); // ── 3) Optional middleware example (header on all responses) ── $app->add(function (Request $request, RequestHandlerInterface $handler): Response { $response = $handler->handle($request); return $response->withHeader('X-Powered-By', 'VibeScriptz + Slim 4'); }); // ── 4) Basic routes ─────────────────────────────────────── $app->get('/', function (Request $request, Response $response): Response { $response->getBody()->write("Hello from Slim 4 👋"); return $response; }); $app->get('/api/ping', function (Request $request, Response $response): Response { $data = ['status' => 'ok', 'time' => date('c')]; $payload = json_encode($data, JSON_PRETTY_PRINT); $response->getBody()->write($payload); return $response->withHeader('Content-Type', 'application/json'); }); // ── 5) JSON stats endpoint: last 7 days ─────────────────── $app->get('/api/stats/daily', function (Request $request, Response $response) use ($pdo): Response { // Optional: read ?days=N from query string, default 7 $queryParams = $request->getQueryParams(); $days = isset($queryParams['days']) ? (int)$queryParams['days'] : 7; if ($days < 1 || $days > 60) { $days = 7; // keep it sane } $stmt = $pdo->prepare(" SELECT day, visits FROM stats_daily WHERE day >= (CURDATE() - INTERVAL :days DAY) ORDER BY day ASC "); $stmt->bindValue(':days', $days, PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(); $payload = json_encode($rows, JSON_PRETTY_PRINT); $response->getBody()->write($payload); return $response ->withHeader('Content-Type', 'application/json'); }); // ── 6) Run app ───────────────────────────────────────────── $app->run(); Hit /api/stats/daily in your browser or via curl and you should get JSON with your stats, ready for charts, dashboards, or widgets on another page.
5. Mapping old F3 / Flight habits to Slim
If you’re coming from F3 or Flight, here’s a quick mental map:
- Flight::route('GET /') →
$app->get('/', fn (...) => ...); - $f3->route('GET /user/@id') →
$app->get('/user/{id}', fn ($req,$res,$args) => ...); - Echoing HTML → Build HTML (or use
ob_start()) and write to$response->getBody(). - Before/after hooks → Slim middleware (like the
X-Powered-Byheader above).
You don’t have to go full “framework brain” to use Slim. For tiny tools, you can keep using plain PHP templates, include files, and simple PDO queries.
6. Where this fits on a webmaster site
On a site like yours, Slim 4 is perfect for:
- /api/ endpoints for lightweight analytics or referrers.
- JSON feeds for galleries, confessions, or “latest scripts”.
- Small admin widgets behind a single middleware-based login check.
- Webhooks from payment providers or other services.
Instead of rewriting everything into a heavy CMS, you can keep your current layout and just let Slim handle the paths that benefit from structured routing and JSON responses.
7. Quick launch checklist
- ✅ Composer installed somewhere you can run it.
- ✅
composer require slim/slim slim/psr7run in your project folder. - ✅
index.phpwired tovendor/autoload.php. - ✅ Apache (or Nginx) rewriting requests into Slim.
- ✅ A simple stats table or existing DB you can read from.
- ✅
/api/stats/dailyreturning JSON without errors.
Once that’s in place, you’ve got a modern, maintained micro-framework that feels like a cleaner, PSR-friendly version of F3/Flight – and it’s already doing real work on your site.
--- If you want, next we can: - Add a little chart snippet that calls/api/stats/daily and renders a graph, or - Do a second VibeScriptz post that’s “Slim 4 JSON stats widget for your dashboard” and links back to this one. ::contentReference[oaicite:0]{index=0}