Working with a Database

Unlike Laravel, which bundles the Eloquent ORM, Slim doesn't include any database layer out of the box. That's intentional — Slim leaves the choice up to you. The simplest option is the one you already know from Core PHP: plain PDO (see PHP & MySQL if you need a refresher on connecting and running prepared statements).

Where this fits: Nothing about PDO changes inside Slim. The only new idea here is where you create the connection and how route handlers get access to it.

Registering PDO in the DI Container

Creating a fresh PDO connection inside every route handler works, but it means you're opening a new database connection on every single request that needs one, and repeating the same connection code everywhere. Instead, register it once as a service in the container (see Dependency Injection) so every route can ask for the same connection:

<?php
require __DIR__ . '/../vendor/autoload.php';

use DI\Container;
use Slim\Factory\AppFactory;

$container = new Container();
AppFactory::setContainer($container);

$container->set('db', function () {
    $dsn = 'mysql:host=localhost;dbname=slim_demo;charset=utf8mb4';

    return new PDO($dsn, 'root', '', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
});

$app = AppFactory::create();

The closure passed to set() only runs the first time something asks the container for 'db' — after that, the same PDO instance is handed back every time, so you get exactly one connection per request instead of one per query.

Querying from a Route Handler

Inside a route closure, $this refers to the container, so pulling the connection out and running a prepared statement looks almost identical to plain PHP:

<?php
$app->get('/users', function ($request, $response) {
    $db = $this->get('db');

    $stmt = $db->prepare('SELECT id, name, email FROM users ORDER BY id DESC LIMIT ?');
    $stmt->bindValue(1, 20, PDO::PARAM_INT);
    $stmt->execute();

    $users = $stmt->fetchAll();

    $response->getBody()->write(json_encode($users));

    return $response->withHeader('Content-Type', 'application/json');
});

Everything about the query itself — the prepared statement, the placeholder, fetchAll() — is exactly the PDO you already know. Slim just gave the handler a ready-to-use connection instead of making it build one from scratch.

Query Builders and ORMs

If writing raw SQL for every query starts to feel tedious as a project grows, you're not stuck with PDO forever. Composer packages like illuminate/database (Eloquent, usable outside Laravel) or Cycle ORM can be added the same way any other dependency is — a composer require away — and wired into the container exactly like the PDO example above.

Next steps: With data flowing in from the database, move on to Templates (Twig) to see how Slim can render full HTML pages, not just JSON.