Getting Started with the Database
Fast's database layer has a neat trick: it uses plain PDO, works with any driver PDO supports, and yet never blocks the event loop. That combination is unusual, and understanding how it works will help you use it well.
How it stays non-blocking
PDO is synchronous. A query call blocks until the database answers. In a normal event-loop server that would be a disaster, freezing every other connection while one slow query runs. Fast sidesteps this by running PDO out of process.
Each worker owns a small pool of executor subprocesses, and each executor holds one PDO connection. When your handler runs a query, the worker ships the SQL and its parameters to an idle executor over a socket, then parks that fiber. The executor does the blocking call in its own process while the event loop happily keeps serving everyone else. When the result comes back, your fiber wakes up with the rows.
The upshot: a slow query only slows down its own request. Concurrent queries run truly in parallel across the executor pool.
Enabling the database
Turn it on with use_database() before serve(), passing a config with your DSN:
use Fast\Database\Config as DbConfig;
use_database(new DbConfig(
dsn: 'mysql:host=127.0.0.1;dbname=app',
username: 'app',
password: 'secret',
poolSize: 4,
));
The connection is opened lazily. Nothing connects until your first query actually runs inside a worker, so enabling the database costs nothing for requests that never touch it.
Do not call
db()in your root process, beforeserve(). Connections are built per worker, on first use. Resolving the database at the root would try to connect in the master, which is not what you want.
Running raw queries
The db() helper is your direct line to the connection. It has a method for each common shape of query:
db()->select('SELECT * FROM users WHERE active = ?', [1]); // all rows
db()->first('SELECT * FROM users WHERE id = ?', [$id]); // one row or null
db()->value('SELECT COUNT(*) FROM users'); // a single scalar
db()->execute('UPDATE users SET seen = ? WHERE id = ?', [$t, $id]); // affected rows
db()->insert('INSERT INTO users (name) VALUES (?)', [$name]); // last insert id
Every value goes in as a bound ? parameter through a prepared statement. Fast never concatenates your data into SQL, which means SQL injection is off the table as long as you keep using parameters. Bound values must reduce to a scalar or null; enums, DateTime, and Stringable objects are converted for you.
Choosing your data API
Raw queries are one of three ways to talk to the database, and they are the right choice when you want full control over the SQL. The other two build on top:
- The Query Builder composes SQL fluently for you.
- Models & Repositories map rows to typed PHP objects.
They all share the same executor pool and the same safety guarantees, so mix and match freely.
Configuration options
The Config object has sensible defaults, but a few options are worth knowing:
| Option | Default | Meaning |
|---|---|---|
dsn | required | The PDO DSN. |
username / password | null | PDO credentials. |
poolSize | 4 | Executor processes per worker. |
maxQueriesPerExecutor | 0 | Recycle an executor after N queries (0 = never). |
acquireTimeout | 10.0 | Seconds a query waits for a free executor. |
Remember that your total connection count is roughly workers × poolSize, so size both together with your database's connection limit in mind.
When you are ready to stop hand-writing SQL, head to the Query Builder.