BeamJS uses a pluggable data access layer architecture that allows support for different databases through custom ModelController implementations. This guide shows how to create a compatible ModelController for new databases like ClickHouse.
var backend = require('beamjs').backend();
var ModelController = function (connectionString, callback, options, databaseKey) {
var self = this;
// Initialize your database connection here
self.connection = createConnection(connectionString, options);
self.databaseKey = databaseKey;
// Handle connection success/failure
self.connection.on('connect', () => callback(null));
self.connection.on('error', (err) => callback(err));
};Retrieves data from database based on query expressions.
self.getObjects = function (queryWrapper, entity, callback) {
try {
var queryExpressions = queryWrapper.getObjectQuery() || [];
var features = entity.getObjectFeatures() || {};
// Convert BeamJS query to your database query
var sql = buildSelectQuery(queryExpressions, features, entity);
self.connection.query(sql, function(err, results) {
if (err) return callback(null, err);
// Handle pagination if needed
if (features.paginate) {
return callback({
modelObjects: results.rows,
pageCount: results.totalCount / features.limit
});
}
callback(results);
});
} catch (error) {
callback(null, error);
}
};Inserts new records into database.
self.addObjects = function (objectsAttributes, entity, callback) {
try {
var tableName = entity.getTableName();
var insertData = Array.isArray(objectsAttributes) ?
objectsAttributes : [objectsAttributes];
var sql = buildInsertQuery(tableName, insertData);
self.connection.query(sql, function(err, results) {
if (err) return callback(null, err);
callback(results.insertedObjects || insertData);
});
} catch (error) {
callback(null, error);
}
};Deletes records from database.
self.removeObjects = function (queryWrapper, entity, callback) {
try {
var queryExpressions = queryWrapper.getObjectQuery() || [];
var tableName = entity.getTableName();
var sql = buildDeleteQuery(tableName, queryExpressions);
self.connection.query(sql, function(err, results) {
if (err) return callback(null, err);
callback(results.deletedCount || 0);
});
} catch (error) {
callback(null, error);
}
};Persists changes to database (for update operations).
self.save = function (callback, session) {
if (!session || session.length === 0) {
return callback(null, []);
}
var savedObjects = [];
var saveNext = function(index) {
if (index >= session.length) {
return callback(null, savedObjects);
}
var obj = session[index];
var sql = buildUpdateQuery(obj);
self.connection.query(sql, function(err, result) {
if (err) return callback(err);
savedObjects.push(result);
saveNext(index + 1);
});
};
saveNext(0);
};ModelController.defineEntity = function (name, attributes, plugins, constraints, databaseKey) {
// Create table schema for your database
var schema = convertAttributesToSchema(attributes, constraints);
// Execute CREATE TABLE if needed
var connection = getConnection(databaseKey);
var sql = buildCreateTableQuery(name, schema);
connection.query(sql, function(err) {
if (err) console.error('Table creation failed:', err);
});
// Return entity constructor if needed
return function EntityConstructor(data) {
Object.assign(this, data);
};
};// clickhouse-controller.js
var ClickHouse = require('@clickhouse/client');
var ClickHouseController = function (connectionString, callback, options, databaseKey) {
var self = this;
self.client = ClickHouse.createClient({
host: options.host || 'localhost:8123',
username: options.username,
password: options.password,
database: options.database
});
// Test connection
self.client.ping().then(() => {
callback(null);
}).catch(callback);
self.getObjects = function (queryWrapper, entity, callback) {
var queryExpressions = queryWrapper.getObjectQuery() || [];
var sql = `SELECT * FROM ${entity.getTableName()}`;
// Build sql from queryExpressions
self.client.query({ query: sql }).then(result => {
result.json().then(data => callback(data));
}).catch(err => callback(null, err));
};
// Implement other required methods...
};
ClickHouseController.defineEntity = function (name, attributes) {
// ClickHouse table creation logic
return function(data) { Object.assign(this, data); };
};
module.exports = ClickHouseController;// In your BeamJS application
var ClickHouseController = require('./clickhouse-controller');
var backend = require('beamjs').backend();
// Set up the controller
backend.setModelController(new ClickHouseController(
connectionString,
function(err) {
if (err) console.error('Database connection failed:', err);
else console.log('ClickHouse connected successfully');
},
options,
'clickhouse'
));- ✅ Implement all 4 required methods
- ✅ Handle BeamJS query expressions
- ✅ Support pagination features
- ✅ Implement defineEntity static method
- ✅ Handle errors properly with callback pattern
- ✅ Convert BeamJS operators to database-specific syntax
- ✅ Support basic CRUD operations
- Fork the BeamJS repository
- Create your ModelController in
src/database/your-database/ - Add tests in
tests/database/your-database.test.js - Update documentation
- Submit pull request
For questions, open an issue on the BeamJS GitHub repository.
Continue reading the documentation: