Implementing RESTful APIs with Phalcon Micro: Routing and DI
Build high-performance REST APIs using Phalcon Micro. This guide covers C-extension verification, Dependency Injection setup, and regex-based routing.
20 Dec 2025, 12:13 UTC

The Challenge: Reducing Framework Overhead
Full-stack MVC frameworks often introduce unnecessary latency and memory consumption for simple microservices. When building a REST API, you need a lean execution path that maps HTTP verbs directly to logic without the overhead of complex view engines or automatic controller discovery. The takeaway is that Phalcon\Mvc\Micro provides a C-extension based minimal footprint that allows you to explicitly define your service container and routing table for maximum performance.
Prerequisites
- PHP 8.1 or newer (required for Phalcon 5.x).
- Administrative access to the server (required to load the Phalcon C-extension).
- Composer installed for dependency management.
- A running MySQL instance for the database example.
Verifying the Phalcon Extension
Because Phalcon is delivered as a compiled C-extension rather than a standard PHP library, it must be loaded in php.ini. Run the following command in your terminal to ensure the extension is active:
php -m | grep phalcon
If no output is returned, you must add extension=phalcon.so (Linux) or extension=phalcon.dll (Windows) to your configuration and restart your web server. Version mismatches between the PHP runtime and the extension will prevent the server from starting.
Bootstrapping the Micro Application
Initialize your project and install the Phalcon components via Composer:
composer init --name=api/phalcon-micro --require=phalcon/phalcon:^5.0
composer install
Create a entry point at public/index.php. This file initializes the Dependency Injector (DI) and the Micro application instance:
<?php
use Phalcon\Mvc\Micro;
use Phalcon\Di\FactoryDefault\Micro as MicroDI;
require __DIR__ . '/../vendor/autoload.php';
// Use MicroDI for a lightweight service container
$di = new MicroDI();
$app = new Micro($di);
// Service registration and routing logic follow
require __DIR__ . '/../app/services.php';
require __DIR__ . '/../app/routes.php';
$app->handle($_SERVER['_url'] ?? '/');
Registering Shared Services
The Dependency Injector (DI) manages shared resources. Using setShared ensures the service is instantiated only once (Singleton pattern), which is critical for database connections to avoid exhausting socket limits.
In app/services.php:
<?php
/** @var \Phalcon\Di\Container $di */
// Database connection service
$di->setShared('db', function () {
return new \Phalcon\Db\Adapter\Pdo\Mysql([
'host' => 'localhost',
'username' => 'api_user',
'password' => 'secret_pass',
'dbname' => 'api_db',
]);
});
// Standardized response object
$di->setShared('response', function () {
return new \Phalcon\Http\Response();
});
Defining RESTful Routes
Map HTTP methods directly to handler functions. Use regex in route parameters to ensure the application only processes valid input types (e.g., numeric IDs).
In app/routes.php:
<?php
/** @var \Phalcon\Mvc\Micro $app */
// GET: Retrieve all records
$app->get('/users', function () use ($app) {
$db = $app->di->get('db');
$users = $db->fetchAll('SELECT id, name FROM users');
return $app->response
->setJsonContent($users)
->setStatusCode(200, 'OK');
});
// POST: Create a record
$app->post('/users', function () use ($app) {
$data = $app->request->getJsonRawBody();
if (!$data || !isset($data->name)) {
return $app->response
->setStatusCode(400, 'Bad Request')
->setJsonContent(['error' => 'Name is required']);
}
$db = $app->di->get('db');
$db->execute('INSERT INTO users (name) VALUES (?)', [$data->name]);
return $app->response
->setStatusCode(201, 'Created')
->setJsonContent(['id' => $db->lastInsertId()]);
});
// PUT: Update a record using regex for ID validation
$app->put('/users/{id:[0-9]+}', function ($id) use ($app) {
$data = $app->request->getJsonRawBody();
$db = $app->di->get('db');
$success = $db->execute('UPDATE users SET name = ? WHERE id = ?', [$data->name, $id]);
return $success
? $app->response->setJsonContent(['status' => 'updated'])
: $app->response->setStatusCode(404, 'Not Found');
});
Verification and Testing
Start the PHP built-in server from the project root:
php -S localhost:8080 -t public
Test the POST endpoint using curl to verify the DI and database integration:
curl -i -X POST -H "Content-Type: application/json" -d '{"name":"Jane Doe"}' http://localhost:8080/users
Expected Result: An HTTP/1.1 201 Created status code and a JSON body containing the new record ID.
Diagnostic Checks and Recovery
| Symptom | Cause | Resolution |
|---|---|---|
Class not found error |
DI service not registered or wrong namespace | Check services.php for correct class paths. |
| 404 Not Found (Valid route) | URL rewriting not configured | Ensure the web server forwards all requests to index.php. |
| Server fails to start | Phalcon extension version mismatch | Match Phalcon version exactly to the PHP runtime version. |
Rollback: If the server becomes unstable after modifying php.ini, remove the extension=phalcon.so line and restart the service to return to a standard PHP environment.
Limitations
- No Auto-Wiring: Unlike some modern frameworks, Phalcon's DI requires explicit registration of services.
- Manual Structure: Micro applications do not provide a default directory structure; you must define your own organization for handlers and services.
- C-Extension Dependency: Deployment requires the ability to install system-level extensions, making it less portable than pure PHP frameworks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.