driftql-react
v1.7.2
Published
DriftQL is a powerful Elegant ORM client designed to simplify data fetching and state management. It provides an intuitive API for making queries and mutations, while also offering features like caching, error handling, and real-time updates. With DriftQL
Maintainers
Readme
DriftQL React 🚀
The bridge between your Tonka Framework application and React. Seamlessly expose your Elegant ORM models to the frontend with a powerful, secure, and declarative API.
🌟 Features
- 🪄 Auto-Data Binding: Inject ORM data directly into DOM attributes (e.g., auto-fill an
img src). - ⚛️ Flexible Usage: Supports both simple attribute injection and advanced Render Props with loading/error states.
- 🔒 Secure by Design: Built-in support for API keys, model whitelisting, and strict SQL sanitization via your DriftQL backend.
- 🎯 Fluent Querying: Use
where,orderBy, andlimitdirectly in your components, just like in your backend code. - 🛠️ Lifecycle Hooks: Handle
onLoadStartandonCompletefor fine-grained control over the loading state.
📦 Installation
npm install driftql-react
# or
yarn add driftql-reactBackend installation:
composer require tonka/driftql⚙️ Configuration
Auto-Configuration (Recommended)
The Tonka Framework provides a command to generate both the frontend and backend configuration files automatically for you.
Run the following command in your terminal:
php tonka driftql:configThis command will create/overwrite:
drift.config.js(Frontend configuration)config/driftql.php(Backend configuration)
Once generated, you can review and customize the files below.
1. Frontend Configuration
The generated drift.config.js file will look like this:
// drift.config.js
export default {
// The base URL of your DriftQL backend bridge
baseURL: '/api/bridge',
// Request timeout in milliseconds
timeout: 5000,
// Cache strategy (RequestCache)
cache: 'default',
// The public key required to authenticate requests with the backend
bridge_public_key: 'd3597f10e13310490809c832b898445e88face0bb7635b846d1b651f62417a29'
};Then, initialize the package in your entry file (e.g., main.tsx or App.js):
import { Client } from 'driftql-react';
import config from './drift.config';
Client.init(config);2. Backend Configuration (Tonka / PHP)
The generated config/driftql.php file manages security, whitelisting, and data access policies.
// config/driftql.php
<?php
return [
/*
|--------------------------------------------------------------------------
| DRIFTQL BRIDGE ENABLED
|--------------------------------------------------------------------------
*/
'enabled' => env('DRIFTQL_BRIDGE_ENABLED', true),
/*
|--------------------------------------------------------------------------
| DRIFTQL BRIDGE KEY
|--------------------------------------------------------------------------
| A secret key that the front-end must provide to access the DriftQL bridge.
| This is an additional layer of security to prevent unauthorized access.
*/
'bridge_public_key' => env('DRIFTQL_BRIDGE_KEY', 'your-public-key-here'),
/*
|--------------------------------------------------------------------------
| MODEL WHITELIST
|--------------------------------------------------------------------------
| List of models (classes) that the front-end is allowed to query.
| If a model is not in this list, the query is rejected immediately.
*/
'whitelist' => [
'allowed_models' => [
\App\Models\User::class,
\App\Models\Employee::class,
// Note : \App\Models\Admin is not here, so it is inaccessible via this route
],
],
/*
|--------------------------------------------------------------------------
| AUTHORIZATION (Policies & Row Level Security)
|--------------------------------------------------------------------------
| Defines access restrictions by Role and by Model.
| The backend automatically applies these "Global Scopes".
|
| Format :
| 'ModelName' => [
| 'RoleName' => [ Filtering rule ]
| ]
|
| Note: You can specify a class implementing \Tonka\DriftQL\Security\Contract
| instead of an array. This class exposes the authorize() method.
*/
'policies' => [
\App\Models\User::class => [
// Admins see everything (no filter)
'admin' => null,
// Editors cannot see other admins
'editor' => [
'column' => 'role',
'operator' => '!=',
'value' => 'admin',
],
// Standard users only see themselves
'user' => [
'column' => 'id',
'operator' => '=',
'value' => 'current_user_id',
],
],
// Example using a Policy Class
\App\Models\Post::class => \App\Contracts\PostContract::class,
],
/*
|--------------------------------------------------------------------------
| HARDCODED LIMITATION (DoS Protection)
|--------------------------------------------------------------------------
| Enforce ceilings to protect database performance.
*/
'limits' => [
// Maximum number of records the front end can request at once
'max_limit' => 100,
// Default number if the front does not specify a limit
'default_limit' => 20,
// Maximum number allowed for the offset (to avoid excessively deep pagination)
'max_offset' => 10000,
],
/*
|--------------------------------------------------------------------------
| SQL SANITIZATION
|--------------------------------------------------------------------------
| Security guidelines for constructing the request.
*/
'security' => [
// The use of "raw SQL" from the front end is strictly prohibited.
// Only structured "where" clauses (column, operator, binding) are accepted.
'allow_raw_sql' => false,
// Checks that the columns requested in 'orderBy' or 'where' actually exist
// in the database schema.
'strict_column_check' => true,
],
];🚀 Usage Examples
1. Basic Configuration
// App.tsx
import { ElegantProvider } from 'driftql-react';
function App() {
return (
<ElegantProvider config={{
timeout: 10000,
cache: true
}}>
<YourApp />
</ElegantProvider>
);
}2. Using useElegant Hook
// shared/UserList.tsx
import { useElegant } from 'driftql-react';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const { all, find, delete: deleteUser } = useElegant<User>('User');
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
all()
.then(setUsers)
.finally(() => setLoading(false));
}, []);
const handleDelete = async (id: number) => {
await deleteUser(id);
setUsers(users.filter(u => u.id !== id));
};
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
<button onClick={() => handleDelete(user.id)}>Delete</button>
</li>
))}
</ul>
);
}3. Using useQueryBuilder Hook
// shared/UserSearch.tsx
import { useQueryBuilder } from 'driftql-react';
function UserSearch() {
const { getBuilder } = useQueryBuilder<User>('User');
const [results, setResults] = useState<User[]>([]);
const search = async (term: string) => {
const builder = getBuilder();
const data = await builder
.where({ column: 'name', operator: 'LIKE', value: `%${term}%` })
.where({ column: 'active', operator: '=', value: true })
.orderBy('name', 'ASC')
.get();
setResults(data);
};
return (
<div>
<input onChange={(e) => search(e.target.value)} placeholder="Search..." />
<ul>
{results.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}4. Define a Model
// models/User.ts
import { Elegant } from 'driftql-react';
export interface UserData {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'moderator';
status: 'active' | 'inactive' | 'pending';
created_at: string;
updated_at: string;
}
export class User extends Elegant implements UserData {
static resourceName = 'User';
id!: number;
name!: string;
email!: string;
role!: 'admin' | 'user' | 'moderator';
status!: 'active' | 'inactive' | 'pending';
created_at!: string;
updated_at!: string;
// Méthodes personnalisées
get isAdmin(): boolean {
return this.role === 'admin';
}
get isActive(): boolean {
return this.status === 'active';
}
get displayName(): string {
return `${this.name} (${this.email})`;
}
}5. CRUD
// CREATE
const newUser = await User.store({
name: 'John Doe',
email: '[email protected]',
role: 'user',
status: 'active'
});
// READ
const user = await User.find(123);
const allUsers = await User.all();
const activeUsers = await User
.where({ column: 'status', operator: '=', value: 'active' })
.get();
// UPDATE
const updated = await User.update(123, {
name: 'Jane Doe',
role: 'admin'
});
// DELETE
await User.delete(123);
// SAVE
const user = new User();
user.name = 'John';
user.email = '[email protected]';
await user.save();
// REFRESH
const user = await User.find(123);
await user.refresh();
// EXISTS
if (await user.exists()) {
console.log('User exists');
}6. Advance Request
// Pagination
const result = await User
.where({ column: 'status', operator: '=', value: 'active' })
.paginate(1, 15);
console.log(result.data); // Users
console.log(result.pagination.total); // Total
// Eager Loading
const users = await User
.with(['posts', 'profile'])
.where({ column: 'status', operator: '=', value: 'active' })
.get();
// Join
const posts = await Post
.join({
resource: 'User',
type: 'inner',
fkey: 'user_id',
okey: 'id'
})
.where({ column: 'users.role', operator: '=', value: 'admin' })
.get();
// Agregation
const stats = await User
.groupBy('role')
.select('role, COUNT(*) as total')
.get();
// Find Or Fail
try {
const user = await User.findOrFail(123);
} catch (error) {
console.error('User not found');
}
// Store Or Fail
try {
const user = await User.storeOrFail({ name: 'John' });
} catch (error) {
console.error('Creation failed');
}📚 API Reference
| Category | Method | Description |
| :--- | :--- | :--- |
| CRUD | find() | Find by ID |
| | findOrFail() | Find or error |
| | all() | All records |
| | store() | Create new record |
| | storeOrFail() | Create or error |
| | update() | Update a record |
| | updateOrFail() | Update or error |
| | delete() | Destroy a record |
| | deleteOrFail() | Destroy or error |
| Instance | with() | Eager-loading |
| | where() | Condition WHERE |
| | having() | Condition HAVING |
| | savg() | Save (create/update) |
| | saveOrFail() | Save or error |
| | refresh() | Refresh |
| | exists() | Verify if exists |
| | toJSON() | Convert to JSON |
| | toFormData() | Convert to FormData |
🔐 Security Note
DriftQL is designed to expose your ORM securely. Ensure your backend whitelist and policies are correctly configured to prevent unauthorized data access. The bridge_public_key ensures that only your frontend application can communicate with the bridge.
📝 License
MIT
