RESTForge

Query Deklaratif

Konfigurasi query berbasis template dengan parameter binding dinamis

RESTForge menggunakan pendekatan deklaratif untuk menentukan query SQL yang digunakan oleh setiap endpoint. Seluruh konfigurasi query didefinisikan di dalam file payload JSON tanpa menulis kode secara manual. Setiap properti query memiliki peran spesifik dan melayani endpoint tertentu.

Daftar Properti Query (Query Properties)

PropertiWajibFile ReferenceEndpoint yang Dilayani
datatablesQueryYa/datatables
viewQueryTidak/read, /first, /read-composite (header)
viewNameTidak/read, /first, /lookup, /read-composite (header)
exportQueryTidak/export
detailQueryTidak/read-composite (detail)

Prioritas Resolusi Query (Query Resolution Priority)

Setiap endpoint menentukan query SQL berdasarkan prioritas resolusi berikut:

/datatables      →  datatablesQuery  →  fallback: SELECT * FROM readSource
/read            →  viewName  →  viewQuery  →  tableName
/first           →  viewName  →  viewQuery  →  tableName
/lookup          →  viewName  →  tableName
/export          →  exportQuery  →  fallback: SELECT {fieldName} FROM {tableName}
/read-composite  →  Header: viewName → viewQuery → tableName
                    Detail: detailQuery → fallback: SELECT * FROM {detailTable}

datatablesQuery tidak menjadi fallback untuk /read. Endpoint /read dan /datatables memiliki sumber data yang independen.

Properti Query secara Detail (Detailed Properties)

datatablesQuery

Query utama yang digunakan oleh endpoint /datatables. Properti ini wajib ada di setiap payload.

Deklarasi inline:

payload/item-product.json
{
    "datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id"
}

Deklarasi file reference:

payload/item-product.json
{
    "datatablesQuery": "file:query/item_product_list.sql"
}

viewQuery

Query yang berfungsi sebagai "virtual view" pengganti database VIEW. Digunakan oleh endpoint /read, /first, dan header pada /read-composite. Cocok untuk situasi di mana pembuatan VIEW di database tidak memungkinkan (misalnya keterbatasan akses DDL).

payload/item-product.json
{
    "viewQuery": "select a.item_product_id, a.product_code, a.product_name, a.category_id, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id"
}

viewName

Nama database VIEW yang sudah dibuat di server database. Jika didefinisikan, endpoint /read, /first, /lookup, dan header pada /read-composite akan membaca data dari VIEW ini. Operasi write (insert, update, delete) tetap menggunakan tableName.

payload/item-product.json
{
    "tableName": "item_product",
    "viewName": "v_item_product"
}

DDL untuk membuat VIEW:

create or replace view v_item_product as
select a.item_product_id, a.product_code, a.product_name,
       a.category_id, b.category_name, a.selling_price,
       a.stock, a.uom, a.is_active
from item_product a
inner join category b on b.category_id = a.category_id;

exportQuery

Query untuk endpoint /export yang menentukan data yang diekspor ke file Excel. Mendukung alias kolom dan transformasi nilai di level SQL.

payload/item-product.json
{
    "exportQuery": "file:query/item_product_export.sql"
}

Contoh file SQL dengan alias bahasa Indonesia:

query/item_product_export.sql
select a.product_code as "Kode Produk",
       a.product_name as "Nama Produk",
       b.category_name as "Kategori",
       a.selling_price as "Harga Jual",
       a.stock as "Stok",
       case when a.is_active then 'Aktif' else 'Tidak Aktif' end as "Status"
from item_product a
inner join category b on b.category_id = a.category_id
order by b.category_name, a.product_name

detailQuery

Query yang digunakan oleh endpoint /read-composite untuk mengambil detail item berdasarkan foreign key dari header. Terletak di dalam struktur masterDetail.detailConfig.

payload/stock-inbound.json
{
    "masterDetail": {
        "enabled": true,
        "detailTable": "stock_inbound_item",
        "foreignKey": "stock_inbound_id",
        "detailConfig": {
            "detailQuery": "file:query/stock-inbound-detailquery.sql"
        }
    }
}

Query harus berisi satu placeholder untuk foreign key value sesuai syntax native database:

DatabasePlaceholderContoh
PostgreSQL$1WHERE stock_inbound_id = $1
MySQL?WHERE stock_inbound_id = ?
Oracle:1WHERE stock_inbound_id = :1

Saat detailQuery menggunakan file reference, isi file SQL harus ditulis dengan placeholder native sesuai database target. Tidak ada konversi placeholder otomatis pada mode file.

Format File Reference

Semua properti query yang mendukung file reference menggunakan format file:relative/path/to/query.sql. Path bersifat relatif terhadap lokasi file payload JSON.

Struktur folder yang direkomendasikan:

payload/
├── item-product.json
├── supplier.json
├── stock-inbound.json
└── query/
    ├── item_product_list.sql
    ├── item_product_export.sql
    ├── stock-inbound-datatables.sql
    └── stock-inbound-detailquery.sql

Contoh Penggunaan (Usage Examples)

Tabel Sederhana tanpa Relasi (Simple Table)

payload/category.json
{
    "tableName": "category",
    "primaryKey": "category_id",
    "fieldName": ["category_id", "category_name", "is_active"],
    "datatablesQuery": "select category_id, category_name, is_active from category",
    "action": {
        "datatables": true,
        "read": true,
        "first": true,
        "create": true,
        "update": true,
        "delete": true
    }
}

Pada tabel sederhana tanpa relasi, cukup definisikan datatablesQuery. Endpoint /read dan /first menggunakan fallback ke tableName secara langsung.

Tabel dengan Relasi — Menggunakan Database VIEW

payload/item-product.json
{
    "tableName": "item_product",
    "primaryKey": "item_product_id",
    "viewName": "v_item_product",
    "fieldName": [
        "item_product_id", "product_code", "product_name",
        "category_id", "category_name", "selling_price", "stock", "is_active"
    ],
    "datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id",
    "action": {
        "datatables": true,
        "read": true,
        "first": true,
        "lookup": true,
        "create": true,
        "update": true
    }
}

Dengan viewName, endpoint /first dan /lookup juga mendapatkan category_name karena membaca dari VIEW.

Tabel dengan Relasi — Tanpa Database VIEW

Situasi yang sama, tetapi menggunakan viewQuery sebagai pengganti karena tidak dapat membuat VIEW di database:

payload/item-product.json
{
    "tableName": "item_product",
    "primaryKey": "item_product_id",
    "fieldName": [
        "item_product_id", "product_code", "product_name",
        "category_id", "category_name", "selling_price", "stock", "is_active"
    ],
    "datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id",
    "viewQuery": "file:query/item_product.sql",
    "action": {
        "datatables": true,
        "read": true,
        "first": true,
        "create": true,
        "update": true
    }
}

Perbedaan dengan viewName: pada konfigurasi viewQuery, endpoint /first dan /lookup membaca langsung dari tableName sehingga tidak mendapatkan kolom dari relasi. Hanya endpoint /read yang mendapatkan data relasi melalui viewQuery.

Master-Detail Composite Read

payload/stock-inbound.json
{
    "tableName": "stock_inbound",
    "primaryKey": "stock_inbound_id",
    "fieldName": [
        "stock_inbound_id", "inbound_number", "inbound_date",
        "warehouse_id", "supplier_id", "total_items", "total_qty", "total_amount", "status"
    ],
    "datatablesQuery": "file:query/stock-inbound-datatables.sql",
    "action": {
        "datatables": true,
        "read": true,
        "readComposite": true,
        "createComposite": true,
        "updateComposite": true
    },
    "masterDetail": {
        "enabled": true,
        "detailTable": "stock_inbound_item",
        "foreignKey": "stock_inbound_id",
        "detailConfig": {
            "tableName": "stock_inbound_item",
            "primaryKey": "stock_inbound_item_id",
            "detailQuery": "file:query/stock-inbound-detailquery.sql"
        }
    }
}

Endpoint /read-composite menggunakan tableName untuk header dan detailQuery untuk detail item yang melakukan JOIN ke item_product untuk mengambil product_code dan product_name.

Hubungan fieldName dengan Query (fieldName and Query)

fieldName berfungsi sebagai whitelist kolom yang memfilter output response. Mekanisme ini berlaku pada semua endpoint read.

SkenarioPerilaku
Query mengembalikan kolom lebih banyak dari fieldNameKolom ekstra dibuang sebelum dikirim ke client
fieldName berisi kolom yang tidak ada di hasil queryKolom tersebut tidak muncul di response (tanpa error)
Kolom dari relasi (JOIN)Harus didaftarkan di fieldName agar muncul di response

fieldName tidak mengubah SQL query yang dieksekusi. Filtering terjadi setelah query dieksekusi, pada level aplikasi melalui formatResponseData().

Panduan Pemilihan (Decision Guide)

KondisiProperti yang Digunakan
Tabel sederhana tanpa relasidatatablesQuery saja
Endpoint /first dan /lookup perlu data relasiviewName (database VIEW)
Tidak dapat membuat VIEW di databaseviewQuery (virtual view)
Kolom export berbeda dari tampilan UIexportQuery
Master-detail dengan JOIN pada detail itemdetailQuery di masterDetail.detailConfig
Query panjang yang sulit dibaca inlineFile reference (file:query/xxx.sql)

Ringkasan Properti vs Endpoint (Property vs Endpoint Summary)

Properti/datatables/read/first/lookup/export/read-composite
datatablesQueryUtama
viewNamePrioritas 1Prioritas 1UtamaHeader: Prioritas 1
viewQueryPrioritas 2Prioritas 2Header: Prioritas 2
exportQueryUtama
detailQueryDetail: Utama
tableNameFallbackFallbackFallbackFallbackFallbackFallback

Langkah Selanjutnya (Next Steps)

On this page