meita / jsonbolt
JSONBolt: fast JSON file database engine with relations and caching for PHP.
Installs: 1
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/meita/jsonbolt
Requires
- php: >=8.0
- ext-json: *
- psr/simple-cache: ^3.0
Suggests
- illuminate/support: Laravel integration (service provider, facade).
- symfony/cache: PSR-16 cache adapter for better performance.
README
JSONBolt is a fast file-based JSON database engine for PHP. It stores collections as JSON files, provides CRUD, a fluent query builder, relationships, and caching. It works in any PHP framework and includes optional Laravel integration.
Composer package: meita/jsonbolt. PHP namespace: Meita\\JsonBolt.
Features
- JSON file storage with atomic writes and file locks
- CRUD operations and bulk inserts
- Fluent query builder with common operators
- Relationships: hasOne, hasMany, belongsTo, belongsToMany
- PSR-16 caching with built-in FileCache and ArrayCache
- Framework-agnostic core with optional Laravel service provider and facade
Requirements
- PHP 8.0+
- ext-json
Installation
composer require meita/jsonbolt
Quick start
<?php use Meita\JsonBolt\Cache\FileCache; use Meita\JsonBolt\Database; $db = new Database(__DIR__ . '/data', [ 'cache' => new FileCache(__DIR__ . '/cache'), 'cache_ttl' => 300, ]); $users = $db->collection('users'); $user = $users->insert([ 'name' => 'Sara', 'email' => 'sara@example.com', ]); $found = $users->find($user['id']); $active = $users ->where('active', true) ->orderBy('name') ->limit(10) ->get();
Data layout
Each collection is stored as a single JSON file:
data/users.jsondata/orders.json
Metadata is stored in data/.dbe.meta.json for ID counters and cache versions.
Configuration options
$db = new Database(__DIR__ . '/data', [ 'id_key' => 'id', // default 'id_strategy' => 'increment', // increment or random 'cache' => $cache, // any PSR-16 cache 'cache_ttl' => 300, // seconds or DateInterval 'cache_enabled' => true, 'cache_prefix' => 'dbe', 'relations' => [/* ... */], ]);
ID strategies
increment(default): auto-increment integer IDs per collectionrandom: 16 hex chars fromrandom_bytes
CRUD operations
$users = $db->collection('users'); $user = $users->insert(['name' => 'Mona']); $users->insertMany([ ['name' => 'Ali'], ['name' => 'Noor'], ]); $user = $users->find(1); $many = $users->findMany([1, 2, 3]); $updated = $users->update(1, ['name' => 'Mona A.']); $deleted = $users->delete(1);
Query builder
$results = $users ->where('age', '>=', 18) ->orWhere('role', 'admin') ->orderBy('name', 'asc') ->offset(10) ->limit(20) ->select('id', 'name', 'email') ->get(); $first = $users->where('email', 'like', '%@example.com')->first(); $count = $users->where('active', true)->count();
Supported operators
=,==,!=,<>,>,>=,<,<=in,not incontains(arrays or substring in strings)starts_with,ends_withbetween(array with [min, max])like(SQL-style % and _ wildcards)
Bulk updates and deletes
$updated = $users->where('active', false)->update(['flagged' => true]); $deleted = $users->where('last_login', '<', '2022-01-01')->delete();
Relationships
Define relations in the database options:
$db = new Database(__DIR__ . '/data', [ 'relations' => [ 'users' => [ 'posts' => [ 'type' => 'hasMany', 'collection' => 'posts', 'foreignKey' => 'user_id', 'localKey' => 'id', ], 'profile' => [ 'type' => 'hasOne', 'collection' => 'profiles', 'foreignKey' => 'user_id', 'localKey' => 'id', ], ], 'posts' => [ 'user' => [ 'type' => 'belongsTo', 'collection' => 'users', 'foreignKey' => 'user_id', 'ownerKey' => 'id', ], 'tags' => [ 'type' => 'belongsToMany', 'collection' => 'tags', 'pivot' => 'post_tag', 'foreignPivotKey' => 'post_id', 'relatedPivotKey' => 'tag_id', 'localKey' => 'id', 'relatedKey' => 'id', ], ], ], ]);
Pivot collection example:
[
{ "post_id": 1, "tag_id": 5 },
{ "post_id": 1, "tag_id": 9 }
]
Use with() for eager loading:
$users = $db->collection('users')->with(['posts', 'profile'])->get(); $posts = $db->collection('posts')->with('tags')->get();
Nested relations are supported:
$users = $db->collection('users')->with('posts.tags')->get();
Tip: For best results, always set foreignKey and localKey explicitly. The
default key guessing is intentionally simple.
Caching
JSONBolt uses PSR-16 caching. You can use any adapter. Built-in caches:
Meita\JsonBolt\Cache\ArrayCache(in-memory)Meita\JsonBolt\Cache\FileCache(fast disk cache)Meita\JsonBolt\Cache\NullCache(disable caching)
use Meita\JsonBolt\Cache\FileCache; $db = new Database(__DIR__ . '/data', [ 'cache' => new FileCache(__DIR__ . '/cache'), 'cache_ttl' => 300, ]);
Cache keys are automatically versioned per collection, so writes invalidate old query caches without clearing the entire cache store.
Full example
<?php require __DIR__ . '/vendor/autoload.php'; use Meita\JsonBolt\Cache\FileCache; use Meita\JsonBolt\Database; $db = new Database(__DIR__ . '/data', [ 'cache' => new FileCache(__DIR__ . '/cache'), 'cache_ttl' => 600, 'relations' => [ 'users' => [ 'posts' => [ 'type' => 'hasMany', 'collection' => 'posts', 'foreignKey' => 'user_id', 'localKey' => 'id', ], ], 'posts' => [ 'user' => [ 'type' => 'belongsTo', 'collection' => 'users', 'foreignKey' => 'user_id', 'ownerKey' => 'id', ], 'tags' => [ 'type' => 'belongsToMany', 'collection' => 'tags', 'pivot' => 'post_tag', 'foreignPivotKey' => 'post_id', 'relatedPivotKey' => 'tag_id', 'localKey' => 'id', 'relatedKey' => 'id', ], ], ], ]); $users = $db->collection('users'); $posts = $db->collection('posts'); $tags = $db->collection('tags'); $postTag = $db->collection('post_tag'); $alice = $users->insert(['name' => 'Alice', 'email' => 'alice@example.com']); $bob = $users->insert(['name' => 'Bob', 'email' => 'bob@example.com']); $post1 = $posts->insert(['user_id' => $alice['id'], 'title' => 'Hello JSONBolt']); $post2 = $posts->insert(['user_id' => $alice['id'], 'title' => 'File DB Tips']); $post3 = $posts->insert(['user_id' => $bob['id'], 'title' => 'Caching Fast']); $tagFast = $tags->insert(['name' => 'fast']); $tagJson = $tags->insert(['name' => 'json']); $postTag->insertMany([ ['post_id' => $post1['id'], 'tag_id' => $tagFast['id']], ['post_id' => $post1['id'], 'tag_id' => $tagJson['id']], ['post_id' => $post2['id'], 'tag_id' => $tagJson['id']], ]); $recent = $posts ->where('title', 'like', '%JSON%') ->orderBy('title', 'asc') ->with(['user', 'tags']) ->get(); print_r($recent); $activeUsers = $users ->where('email', 'contains', '@example.com') ->with('posts') ->get(); print_r($activeUsers);
Laravel integration (optional)
- Require Laravel support:
composer require illuminate/support
- Register the service provider and facade:
// config/app.php 'providers' => [ Meita\JsonBolt\Laravel\DBEServiceProvider::class, ], 'aliases' => [ 'DBE' => Meita\JsonBolt\Laravel\Facades\DBE::class, ],
- Publish config (optional):
php artisan vendor:publish --tag=dbe-config
Example in Laravel:
use Meita\JsonBolt\Database; $db = app(Database::class); $users = $db->collection('users')->where('active', true)->get();
Notes and limitations
- This is a file-based engine; it loads a collection into memory on read.
- Writes replace the entire collection file (with locks and atomic rename).
- For very large datasets, consider a dedicated database server.
License
MIT