jengo/api
Status: In Active Development • Pre-Release
jengo/api is an automated, configuration-driven REST API and OpenAPI/Swagger engine for CodeIgniter 4 and the Jengo Framework. It provides declarative resource routing, schema-driven querying, atomic bulk transactional writes, validation form mapping, and real-time interactive Swagger documentation.
Key Capabilities
- Automated REST Resource Routing: Expose full CRUD endpoints automatically with clean HTTP verb mapping (
GET,POST,PUT,PATCH,DELETE). - Interactive OpenAPI & Swagger UI: Auto-generates complete OpenAPI 3.0 specs and embeds an interactive Swagger UI with zero manual annotation upkeep.
- Declarative Resource Configurations: Define access policies, versioning, allowed relations, and validation rules in clean
ResourceConfigclasses. - Relational Tree Derivations: Query and resolve deeply nested relationship graphs on the fly using
?derive=relation_name. - Atomic Bulk & Nested Writes: Batch array insertions and nested relationship writes executed sequentially inside database transaction savepoints.
- Database Key Obfuscation: Native integration with Sqids to hash and decode auto-increment primary and foreign keys automatically.
- Granular Validation Mapping: Bind HTTP verbs directly to specific FormHandler or validation classes.
- Lifecycle Mutation Hooks: Intercept records before and after queries and saves via
beforeQuery,afterQuery,beforeSave, andafterSave. - Version Mutation Chaining: Chain and serve multiple concurrent API versions from a single routing declaration.
Installation
Install the package via Composer:
composer require jengo/apiPublish the configuration file using the Jengo Spark CLI:
php spark jengo:api setupThis publishes app/Config/JengoApi.php.
Defining Resource Configurations
Resource configurations extend Jengo\Api\Support\ResourceConfig to define endpoint behavior, relationship permissions, and validation bindings.
namespace App\Api;
use Jengo\Api\Support\ResourceConfig;
use Jengo\Api\Support\HookContext;
use App\Forms\CreateUserForm;
use App\Forms\UpdateUserForm;
class UserResourceConfig extends ResourceConfig
{
/**
* Target API version constraint
*/
protected $version = 'v1';
/**
* Allowed relationships to derive via ?derive=
*/
protected array $allowedRelations = ['profile', 'posts'];
/**
* Fields whose IDs should be obfuscated with Sqids
*/
protected array $obfuscatedFields = ['id'];
/**
* Maximum pagination limit allowed for this resource
*/
protected int $maxLimit = 100;
/**
* HTTP verbs requiring authentication
*/
protected array $requiredAuth = ['post', 'put', 'patch', 'delete'];
/**
* Validation FormHandler classes mapped to HTTP verbs
*/
protected $formClass = [
'post' => CreateUserForm::class,
'put' => UpdateUserForm::class,
];
/**
* Resource URL identifier (e.g. /api/v1/users)
*/
public function name(): string
{
return 'users';
}
/**
* Modify data before saving
*/
public function beforeSave(array $data, ?HookContext $context = null): array
{
if (isset($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
return $data;
}
}NOTE
To use database key obfuscation, your database Entity class must extend Jengo\Base\Entities\BaseEntity and declare the targeted fields in its $obfuscatedFields property.
Registering Resources
Register your resource configurations in app/Config/JengoApi.php:
namespace Config;
use Jengo\Api\Config\JengoApi as BaseJengoApi;
use App\Api\UserResourceConfig;
use App\Api\PostResourceConfig;
class JengoApi extends BaseJengoApi
{
public string $apiName = 'My Application API';
public string $apiBaseUrl = '/api';
public array $resources = [
UserResourceConfig::class,
PostResourceConfig::class,
];
}Publishing Routes & Swagger Documentation
Publish versioned endpoints, JSON OpenAPI specifications, and interactive Swagger UI in app/Config/Routes.php:
// app/Config/Routes.php
use Jengo\Api\Router;
use Jengo\Api\Support\RouterOptions;
use Jengo\Api\Support\DocsOptions;
Router::publish($routes, new RouterOptions(
version: 'v1',
docs: new DocsOptions(
route: 'docs', // Serves OpenAPI JSON at /api/v1/docs
uiRoute: 'docs/ui' // Serves interactive Swagger UI at /api/v1/docs/ui
)
));Multi-Version Routing
Chain mutations dynamically to serve multiple API versions concurrently:
Router::publish($routes, new RouterOptions(version: 'v1'))
->mutate(new RouterOptions(version: 'v2'));Query Parameters
Endpoints support standardized URL query parameters:
1. Pagination
GET /api/v1/users?page=2&limit=15Returns standard pagination envelopes:
{
"status": "success",
"data": [...],
"pagination": {
"page": 2,
"limit": 15,
"total": 120,
"totalPages": 8,
"hasMore": true,
"nextPage": 3,
"prevPage": 1
}
}2. Sorting
Prefix with - for descending order:
GET /api/v1/users?sort=-created_at3. Full-Text Search
GET /api/v1/users?search=Alice4. Relational Tree Derivations
Derive relationships defined in $allowedRelations:
GET /api/v1/users?derive=profile,postsRelational Mutations & Bulk Writes
All mutation operations are processed atomically inside database transaction savepoints. If any single validation rule or hook fails, the entire transaction is rolled back.
Standard Resource Creation
POST /api/v1/users
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com"
}Bulk Array Insertions (Batch POST)
Send an array of records to create multiple rows in a single atomic transaction:
POST /api/v1/users
Content-Type: application/json
[
{ "name": "John Doe", "email": "john@example.com" },
{ "name": "Jane Doe", "email": "jane@example.com" },
{ "name": "Bob Smith", "email": "bob@example.com" }
]Nested Relational Writes
Create root resources and their child relations simultaneously:
POST /api/v1/users
Content-Type: application/json
{
"name": "Alice Smith",
"email": "alice@example.com",
"posts": [
{ "title": "First Blog Post", "content": "Hello World..." },
{ "title": "Second Blog Post", "content": "More content..." }
]
}Lifecycle Hooks
ResourceConfig classes support four lifecycle hooks:
// Intercept query builder before execution
public function beforeQuery($query, ?HookContext $context = null): void
{
// Apply multi-tenant scope
$query->where('tenant_id', session('tenant_id'));
}
// Modify fetched records before formatting
public function afterQuery(array $data, ?HookContext $context = null): array
{
return $data;
}
// Intercept data before saving (insert or update)
public function beforeSave(array $data, ?HookContext $context = null): array
{
return $data;
}
// Inspect saved records post-save
public function afterSave(array $record, ?HookContext $context = null): array
{
return $record;
}CLI Commands
Scaffolding a Resource Configuration
Use the Jengo Master Command generator:
php spark jengo:make api_resource UserResourceThis scaffolds app/Api/UserResource.php.
Publishing Configuration
php spark jengo:api setup