Links

Query interface and Native Queries

Forest Admin can connect to any data source, as long as it can be represented as a collection of records that have a common structure.
To achieve that, Forest Admin needs to abstract away data source differences: each connector "speaks" the language of a given API on one side and exposes the Forest Admin Query Interface on the other.
This interface is called the Forest Admin Query Interface, it is not a full-featured ORM: its objective is to be "just enough" to fuel Forest Admin as keeping it small makes it easier to implement and maintain custom data sources for customers.

Choosing how to query your data

The Forest Admin Query Interface is used to implement all native features of the admin panel, however, when writing custom code (creating new actions, fields, ...), you can either access your data using the Forest Admin Query Interface or using the native driver.
The choice is yours, and you will probably use both depending on the situation.
-
Forest Admin Query Interface
Native Driver
Code consistency
👍 Use the same query interface for all data sources
👎 Different API for each database / SaaS
Customizations can be queried (computed field, relationships, ...)
👍 Yes
👎 No
Features
👎 Common denominator is exposed
👍 All features of the underlying API
In-app deployments
👎 Difficult to reuse your existing code
👍 Re-use your existing code
Learning curve
👎 The interface is Forest Admin specific
👍 You already know how to write SQL
Native support for filters from the UI
👍 Yes
👎 No
Total
3 x 👍 + 3 x 👎
3 x 👍 + 3 x 👎

In practice

Querying with the native driver

As the name implies, native drivers have different interfaces for each data source.
It is accessible during customization on the nativeDriver property of each collection and documented in each data source documentation.
SQL
MongoDB
Using the @forestadmin/datasource-sql connector.
collection.addSegment('mySegment', async context => {
const rows = await context.collection.nativeDriver.rawQuery(`
SELECT product_id, COUNT(*)
FROM orders
GROUP BY product_id
ORDER BY count DESC
LIMIT 10;
`);
return { field: 'id', operator: 'In', value: rows.map(r => r['product_id']) };
});
collection.addSegment('mySegment', async context => {
const rows = await context.collection.nativeDriver.model.aggregate([
{ $group: { _id: '$product_id', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 10 },
]);
return { field: 'id', operator: 'In', value: rows.map(r => r['product_id']) };
});

Querying with the Forest Admin Query Interface

Queries can be executed directly, by calling the methods exposed by context.dataSource and context.collection.
collection.addSegment('mySegment', async context => {
const rows = await context.dataSource
.getCollection('orders')
.aggregate({}, { operation: 'Count', groups: [{ field: 'product_id' }] }, 10);
return { field: 'id', operator: 'In', value: rows.map(r => r['product_id']) };
});
Data Source Interface
interface DataSource {
/** Retrieve list of all collection within the data source */
get collections(): Collection[];
/** Get collection by name */
getCollection(name: string): Collection;
}
Collection Interface
Parameters are explained in depth on the following pages:
interface Collection {
/** The schema contains the structure and capabilities of the collection */
get schema(): CollectionSchema;
/** Create new records */
create(data: RecordData[]): Promise<RecordData[]>;
/** List records matching filter */
list(filter: PaginatedFilter, projection: Projection): Promise<RecordData[]>;
/** Update records matching filter */
update(filter: Filter, patch: RecordData): Promise<void>;
/** Delete records matching filter */
delete(filter: Filter): Promise<void>;
/** Compute aggregated version of records matching filter */
aggregate(
filter: Filter,
aggregation: Aggregation,
limit?: number,
): Promise<AggregateResult[]>;
}