QEloquent is a powerful, flexible, and developer-friendly ORM (Object-Relational Mapping) library for Qt 6. Inspired by Laravel's Eloquent, it brings a fluent, expressive API to the world of C++ and Qt, making database interactions as simple as possible.
Built on top of Qt's robust Meta-Object system, QEloquent allows you to define models using standard Qt properties and metadata, offering a "zero-config" experience for most use cases.
β¨ Features
- Eloquent-like API: Fluent and expressive syntax that feels natural to Laravel and RoR developers.
- Qt Integration: Deeply integrated with
Q_GADGET, Q_PROPERTY, and QSqlDatabase.
- Zero-Config Models: Uses Qt's meta-data (
Q_CLASSINFO) for table naming and primary key detection.
- Powerful Relations: Built-in support for
hasOne, belongsTo, hasMany relationships with transparent loading.
- Friendly API: Support for range-based for loops and pointer-like access to related models.
- Fluent Query Builder: Chainable methods for building complex SQL queries without writing raw SQL.
- JSON Support: Seamless conversion to and from
QJsonObject for modern API integration.
- Beautiful Debugging:
qDebug() << model produces readable, nested YAML parsable output.
π¦ Installation
Requirements
- Qt 6.x (with Sql module)
- CMake 3.30+
- C++20 compliant compiler (GCC 10+, Clang 10+, or MSVC 2019+)
Building from Source
git clone https://github.com/commander15/QEloquent.git
cd QEloquent
mkdir build && cd build
cmake -S .. -DCMAKE_PREFIX_PATH=/path/to/Qt6 -DCMAKE_INSTALL_PREFIX=/path/to/prefix
cmake --build . --target install
π Getting Started
1. Database Connection
Before using models, set up a database connection.
#include <QEloquent/connection.h>
void setup() {
if (!conn.open()) {
qCritical() << "Failed to open database:" << conn.lastError().text();
return;
}
}
static Connection addConnection(const QString &name, const QUrl &url)
Adds a new connection using a URL.
Definition connection.cpp:277
2. Defining your Model
To define a model in QEloquent, inherit from QEloquent::Model and QEloquent::ModelHelpers<YourModel> (the latter unlocks all the powerful static methods like find(), all(), create(), etc.).
QEloquent heavily leverages Qt's meta-object system, so most configuration is done via Q_PROPERTY and Q_CLASSINFO. This gives you a zero-config experience by default, while still offering full control when needed.
#include <QEloquent/model.h>
#include <QEloquent/modelhelpers.h>
{
Q_GADGET
Q_PROPERTY(int id MEMBER id)
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString email MEMBER email)
Q_PROPERTY(QString password MEMBER password)
Q_CLASSINFO("table", "users")
Q_CLASSINFO("naming", "Laravel")
Q_CLASSINFO("hidden", "password, roleId")
Q_CLASSINFO("fillable", "name, email, password")
Q_CLASSINFO("append", "emailHeader")
Q_CLASSINFO("with", "role")
QELOQUENT_MODEL_HELPERS(User)
public:
int id = 0;
QString name;
QString email;
QString password;
Q_INVOKABLE QString emailHeader() const
{
return name + " <" + email + ">";
}
{
return belongsTo<UserRole>("role_id", "id");
}
{
return belongsToMany<UserGroup>("UserGroupMembers", "user_id", "group_id");
}
};
Provides static CRUD and query helpers for Model types.
Definition modelhelpers.h:57
The Model class is the base class for all ORM models.
Definition model.h:31
User-facing class for managing model relationships.
Definition relation.h:88
Additional note
- For any property (standard Q_PROPERTY, appended attribute, or relation), you can manually override the database field name used for serialization using Q_CLASSINFO("<propertyName>Field", "custom_field_name"). This bypasses the naming convention entirely for that specific property.
- Both append and with require a matching parameterless Q_INVOKABLE method with the exact same name as the entry. QEloquent uses the Qt meta-system to discover and call these automatically.
This approach keeps your models clean, expressive, and highly configurable without boilerplate.
π Usage Guide
Basic CRUD Operations with Error Handling
Create
You can create models in many ways, you can for example use the create method provided by the helper so it can persist in DB before providing the model instance.
Note: You can also create the instance by yourself fill the data on it the way you want and call save() on it.
QJsonObject data;
data["name"] = "John Doe";
auto result = User::create(data);
if (result) {
User user = result.value();
qDebug() << "Created user with ID:" << user.id;
} else {
qWarning() << "Create failed:" << result.error().text();
}
Read (Find)
There is many way to find a model, you can relay on find helper or just construct a default one, set it property value and call get() on it.
auto result = User::find(1);
if (result) {
User user = result.value();
qDebug() << "Found:" << user.name;
} else {
qWarning() << "User not found or DB error:" << result.error().text();
}
auto users = User::find(User::query().where("name", "John Doe"));
if (!users) {
qWarning() << "Query failed:" << users.error().text();
}
Update & Delete
Just as bellow or retreive as you can and call deleteData() on the model.
auto result = User::find(1);
if (result) {
User user = result.value();
user.name = "Jane Doe";
if (!user.save()) {
qWarning() << "Update failed:" << user.lastError().text();
}
if (!user.deleteData()) {
qWarning() << "Delete failed:" << user.lastError().text();
}
}
π User-Friendly Relationships
QEloquent relations support **transparent loading**βthe database query is executed automatically the first time you access the relation data.
One to Many (Iteration)
auto result = Category::find(1);
if (result) {
Category category = result.value();
for (const Product &product : category.products()) {
qDebug() << "Product:" << product.name;
}
}
Single Relation (Pointer-like Access)
For hasOne or belongsTo, use the -> operator for direct access to the related model's properties.
auto result = Product::find(i);
if (result) {
Product product = result.value();
qDebug() << "Category Name:" << product.category()->name;
}
Implicit Conversion
You can assign a relation directly to a model variable. If no related model exists, you get a default-constructed model.
Stock stock = product.stock();
if (stock.exists()) {
qDebug() << "Stock level:" << stock.quantity;
}
π Advanced Querying
QEloquent use the Query class for high and low level data filtering, that is why you can only use database field name on it (no properties there), the query() function from ModelHelpers<YourModel> help you create an empty query that contains enough informations to build a request targeting the model database table using QEloquent query builder, usefull for advanced data queries without going fully RAW SQL.
Below, you will see how we use it to query models based on filters:
auto query = User::query()
.where("active", true)
.andWhere("age", ">", 18)
.orderBy("created_at", Qt::DescendingOrder)
.limit(10);
auto results = User::find(query);
if (results) {
for (const User &user : results) {
qDebug() << "User: " << user.name;
}
}
π License
LGPLv3 (GNU Lesser General Public License version 3)
Note
If you distribute an application that uses QEloquent under the LGPL, you must include a visible acknowledgment such as: "This software uses QEloquent - https://github.com/commander15/QEloquent" preferably in the About dialog or credits screen.
Made with β₯οΈ by Amadou Benjamain