RESTForge

Processor

Membuat custom endpoint dengan implementasi SQL atau JavaScript

Processor adalah mekanisme RESTForge untuk membuat custom endpoint dengan business logic yang ditulis secara manual. Berbeda dengan CRUD module yang sepenuhnya di-generate dari payload, processor menyediakan scaffold awal (router dan file processor) yang kemudian diisi dengan logic sesuai kebutuhan.

Referensi Cepat (Quick Reference)

PropertiNilai
Perintah generatornpx restforge-cli processor --project={project} --endpoint={endpoint} --payload={payload}.json
Lokasi processorsrc/modules/{project}/processor/{endpoint}/{name}.js
Lokasi routersrc/modules/{project}/{endpoint}.js
Processor signatureasync process(input, services, req)
Services{ db, logger, redis, kafka, cache }
Overwrite-safeFile processor tidak di-overwrite (kecuali --force)
DatabasePostgreSQL, MySQL, Oracle

Perbedaan dengan CRUD Module (Comparison with CRUD Module)

AspekCRUD ModuleProcessor
TujuanOperasi CRUD standar (datatables, create, update, delete, get)Endpoint dengan business logic custom
LogicSepenuhnya di-generate dari payloadScaffold awal di-generate, logic ditulis manual
RegenerasiAman di-regenerate kapan sajaFile processor tidak di-overwrite jika sudah ada
ContohManajemen data supplier, product, warehouseKonfirmasi stock inbound, pembatalan, approval flow

Struktur File (File Structure)

Perintah generator menghasilkan 2 file:

npx restforge-cli processor --project=mini-inventory --endpoint=stock-inbound-process --payload=stock_inbound_process.json
src/modules/mini-inventory/
├── stock-inbound-process.js                          ← Router (generated, JANGAN diubah)
└── processor/
    └── stock-inbound-process/
        └── confirm-inbound.js                        ← Processor (tulis logic DI SINI)

Peran Masing-masing File (Role of Each File)

Router (stock-inbound-process.js):

  • Menerima HTTP request
  • Mengumpulkan input dari body, params, dan query
  • Memanggil processor.process(input, services, req)
  • Mengembalikan response ke client
  • Selalu di-overwrite setiap kali generator dijalankan

Processor (processor/stock-inbound-process/confirm-inbound.js):

  • Tempat menulis custom business logic
  • Menerima input, services, dan req dari router
  • Menjalankan validasi, query database, dan logic bisnis
  • Tidak di-overwrite jika sudah ada (kecuali --force)

Jangan menulis custom logic di file router. File ini selalu di-overwrite setiap kali generator dijalankan, sehingga seluruh custom code yang ditulis di dalamnya akan hilang.

Visualisasi Alur (Flow)

HTTP Request


Router (generated, jangan diubah)
  │   Mengumpulkan input dari body/params/query
  │   Memanggil processor.process(input, services, req)


Processor (custom business logic)       ← TULIS LOGIC DI SINI
  │   Validasi input
  │   Query database
  │   Business logic
  │   Return { success, statusCode, message, data }


Router
  │   res.status(result.statusCode || 200).json(result)


HTTP Response

Payload Processor (Processor Payload)

Payload processor mendefinisikan endpoint, HTTP method, SQL query awal, dan parameter request.

payload/stock_inbound_process.json
{
    "version": "2.0",
    "description": "Stock Inbound processing endpoints",
    "processor": [
        {
            "name": "confirm-inbound",
            "method": "POST",
            "description": "Konfirmasi stock inbound dan update stok produk",
            "sql": {
                "query": "SELECT stock_inbound_id, inbound_number, status, total_qty FROM stock_inbound WHERE stock_inbound_id = $1 AND status = 'draft'",
                "params": ["stock_inbound_id"]
            },
            "request": {
                "body": {
                    "stock_inbound_id": { "type": "uuid", "required": true, "description": "ID Stock Inbound" },
                    "notes": { "type": "string", "required": false, "description": "Catatan konfirmasi" }
                }
            },
            "response": {
                "message": {
                    "success": "Stock inbound berhasil dikonfirmasi.",
                    "empty": "Stock inbound tidak ditemukan atau bukan berstatus draft.",
                    "error": "Gagal mengkonfirmasi stock inbound."
                }
            }
        }
    ]
}

Properti Payload (Payload Properties)

PropertiWajibKeterangan
versionYaVersi payload, gunakan "2.0"
descriptionTidakDeskripsi umum endpoint group
processor[].nameYaNama processor, menjadi nama file dan segmen URL
processor[].methodYaHTTP method: GET, POST, PUT, DELETE
processor[].descriptionTidakDeskripsi processor
processor[].sql.queryYaSQL query awal (scaffold)
processor[].sql.paramsYaArray nama parameter untuk query
processor[].sql.fileTidakAlternatif: path ke file .sql eksternal
processor[].request.bodyTidakDefinisi field dari request body
processor[].request.paramsTidakDefinisi URL params (menjadi /:param di route)
processor[].request.headersTidakDefinisi headers dengan mapTo untuk mapping ke input
processor[].response.messageTidakPesan response untuk success, empty, dan error

SQL dari File Eksternal (External SQL File)

Query SQL yang panjang dapat disimpan di file terpisah:

{
    "sql": {
        "file": "query/confirm-inbound.sql",
        "params": ["stock_inbound_id"]
    }
}

File SQL disimpan di folder payload/query/ relatif terhadap lokasi payload.

Processor API Contract

Signature

const processor = {
    async process(input, services, req) {
        // Business logic
        return { success, statusCode, message, data, timestamp };
    }
};

module.exports = processor;

Parameter

ParameterTipeKeterangan
inputobjectGabungan dari req.body, req.params, dan req.query
servicesobjectInjected services: { db, logger, redis, kafka, cache }
reqobjectExpress request object untuk akses ke headers, ip, method, originalUrl

Services yang Tersedia (Available Services)

ServiceKeyKeterangan
Databasedb{ executeQuery, executeTransaction, getPool }, auto-route berdasarkan DB_TYPE
Loggerloggerpino logger instance
RedisredisRedis client (null jika tidak dikonfigurasi)
KafkakafkaKafka service (null jika KAFKA_ENABLED bukan 'true')
CachecacheCache manager (null jika cache tidak dikonfigurasi)
async process(input, services, req) {
    const { db, logger, redis, kafka, cache } = services;

    // Database
    const result = await db.executeQuery('SELECT * FROM stock_inbound WHERE stock_inbound_id = $1', [input.stock_inbound_id]);

    // Logger (pino)
    logger.info({ event: 'confirm-inbound' }, 'Inbound confirmed');

    // Redis (jika dikonfigurasi)
    if (redis) await redis.del(`stock:${input.stock_inbound_id}`);

    // Kafka (jika dikonfigurasi)
    if (kafka) await kafka.send('topic', { event: 'inbound_confirmed', id: input.stock_inbound_id });
}

Express Request Object

Parameter req memberikan akses ke informasi HTTP yang tidak ada di input:

async process(input, services, req) {
    const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim()
        || req.headers['x-real-ip']
        || req.connection?.remoteAddress;

    const userAgent = req.headers['user-agent'];
    const authHeader = req.headers['authorization'];
}

Parameter req bersifat opsional. Processor yang tidak memerlukan akses ke HTTP request cukup mendeklarasikan process(input, services) tanpa parameter ketiga.

Response Object

PropertiWajibDefaultKeterangan
successYa-true atau false
statusCodeTidak200HTTP status code (200, 400, 404, 500, dll.)
messageYa-Pesan response
dataTidak-Data response (object atau array)
timestampTidak-Timestamp ISO 8601

Contoh Penggunaan (Usage Examples)

Query Database Sederhana

Processor untuk mengambil data stock inbound berdasarkan nomor inbound:

src/modules/mini-inventory/processor/stock-inbound-process/confirm-inbound.js
const processor = {
    async process(input, services) {
        const { db, logger } = services;

        // 1. Validasi input
        if (!input.stock_inbound_id) {
            return {
                success: false,
                statusCode: 400,
                message: 'Parameter stock_inbound_id wajib diisi.'
            };
        }

        // 2. Query stock inbound
        const result = await db.executeQuery(
            'SELECT stock_inbound_id, inbound_number, status, total_qty FROM stock_inbound WHERE stock_inbound_id = $1',
            [input.stock_inbound_id]
        );

        const inbound = result.rows?.[0];
        if (!inbound) {
            return {
                success: false,
                statusCode: 404,
                message: 'Stock inbound tidak ditemukan.'
            };
        }

        // 3. Validasi status
        if (inbound.status !== 'draft') {
            return {
                success: false,
                statusCode: 422,
                message: `Stock inbound tidak dapat dikonfirmasi. Status saat ini: ${inbound.status}.`
            };
        }

        // 4. Update status
        await db.executeQuery(
            'UPDATE stock_inbound SET status = $1, confirmed_at = NOW() WHERE stock_inbound_id = $2',
            ['confirmed', input.stock_inbound_id]
        );

        logger.info({ stockInboundId: input.stock_inbound_id }, 'Stock inbound confirmed');

        return {
            success: true,
            statusCode: 200,
            message: 'Stock inbound berhasil dikonfirmasi.',
            data: {
                stock_inbound_id: inbound.stock_inbound_id,
                inbound_number: inbound.inbound_number,
                status: 'confirmed',
                total_qty: inbound.total_qty
            },
            timestamp: '2026-04-16T10:30:00.000Z'
        };
    }
};

module.exports = processor;

Request:

POST /api/mini-inventory/stock-inbound-process/confirm-inbound
{
    "stock_inbound_id": "550e8400-e29b-41d4-a716-446655440000"
}

Response (200 OK):

{
    "success": true,
    "statusCode": 200,
    "message": "Stock inbound berhasil dikonfirmasi.",
    "data": {
        "stock_inbound_id": "550e8400-e29b-41d4-a716-446655440000",
        "inbound_number": "INB-2026-0001",
        "status": "confirmed",
        "total_qty": 150
    },
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Multiple Processor dalam Satu Payload

Satu file payload dapat mendefinisikan beberapa endpoint sekaligus melalui array processor[]. Setiap item dalam array menghasilkan route terpisah dan file processor terpisah.

Payload:

payload/stock_inbound_process.json
{
    "version": "2.0",
    "description": "Stock Inbound processing endpoints",
    "processor": [
        {
            "name": "confirm-inbound",
            "method": "POST",
            "description": "Konfirmasi stock inbound dan update stok produk",
            "sql": {
                "query": "SELECT stock_inbound_id, inbound_number, status FROM stock_inbound WHERE stock_inbound_id = $1 AND status = 'draft'",
                "params": ["stock_inbound_id"]
            },
            "request": {
                "body": {
                    "stock_inbound_id": { "type": "uuid", "required": true }
                }
            },
            "response": {
                "message": {
                    "success": "Stock inbound berhasil dikonfirmasi.",
                    "empty": "Stock inbound tidak ditemukan atau bukan berstatus draft."
                }
            }
        },
        {
            "name": "cancel-inbound",
            "method": "POST",
            "description": "Batalkan stock inbound",
            "sql": {
                "query": "SELECT stock_inbound_id, inbound_number, status FROM stock_inbound WHERE stock_inbound_id = $1 AND status NOT IN ('cancelled', 'confirmed')",
                "params": ["stock_inbound_id"]
            },
            "request": {
                "body": {
                    "stock_inbound_id": { "type": "uuid", "required": true },
                    "cancel_reason": { "type": "string", "required": true }
                }
            },
            "response": {
                "message": {
                    "success": "Stock inbound berhasil dibatalkan.",
                    "empty": "Stock inbound tidak ditemukan atau sudah dikonfirmasi/dibatalkan."
                }
            }
        }
    ]
}

Perintah generate:

npx restforge-cli processor --project=mini-inventory --endpoint=stock-inbound-process --payload=stock_inbound_process.json

Struktur file yang dihasilkan:

src/modules/mini-inventory/
├── stock-inbound-process.js                           ← Router (1 file, 2 route)
└── processor/
    └── stock-inbound-process/
        ├── confirm-inbound.js                         ← Processor 1: draft → confirmed
        └── cancel-inbound.js                          ← Processor 2: draft → cancelled

Route yang dihasilkan:

MethodURLFungsi
POST/api/mini-inventory/stock-inbound-process/confirm-inboundKonfirmasi stock inbound
POST/api/mini-inventory/stock-inbound-process/cancel-inboundBatalkan stock inbound

Konvensi Penamaan (Naming Convention)

Penamaan yang konsisten sangat penting karena generator secara langsung menggunakan nilai --endpoint dan processor[].name sebagai nama file, nama folder, dan segmen URL.

Anatomi URL

/api / {--project} / {--endpoint} / {processor[].name}
  │         │               │               │
  │         │               │               └── Aksi spesifik
  │         │               └── Kelompok resource
  │         └── Domain atau modul bisnis
  └── Prefix API global

Aturan Penamaan

KomponenJenis KataFormatContoh
--projectKata benda, domainkebab-casemini-inventory, sales, hr
--endpointKata benda, resourcekebab-casestock-inbound-process, employee
processor[].nameKata kerja, aksikebab-caseconfirm-inbound, cancel-inbound

Suffix -process

Jika sebuah entitas sudah memiliki CRUD module, tambahkan suffix -process pada --endpoint processor untuk menghindari konflik file router:

stock-inbound           → digunakan oleh CRUD Module
stock-inbound-process   → digunakan oleh Processor

Kedua file router berdampingan tanpa konflik:

src/modules/mini-inventory/
├── stock-inbound.js              ← CRUD router
├── stock-inbound-process.js      ← Processor router
└── processor/
    └── stock-inbound-process/
        ├── confirm-inbound.js
        └── cancel-inbound.js

Suffix -process hanya diperlukan jika entitas sudah memiliki CRUD module dengan nama yang sama. Jika belum ada CRUD module, nama entitas dapat digunakan langsung tanpa suffix.

Perintah Generator (Generator Commands)

Perintah Dasar

npx restforge-cli processor --project={project} --endpoint={endpoint} --payload={payload}.json

Parameter

ParameterWajibKeterangan
--projectYaDomain/modul bisnis (contoh: mini-inventory)
--endpointYaNama resource group (contoh: stock-inbound-process)
--payloadYaNama file payload di folder payload/
--forceTidakForce overwrite processor file yang sudah ada

Perilaku Generator (Generator Behavior)

FileSudah Ada?Perilaku
Router ({endpoint}.js)YaSelalu di-overwrite
Router ({endpoint}.js)TidakDibuat baru
Processor (processor/{endpoint}/{name}.js)YaDi-skip (tidak di-overwrite)
Processor (processor/{endpoint}/{name}.js)TidakDibuat baru
Processor (dengan --force)YaDi-overwrite (file lama di-archive)

1. Buat Payload

Definisikan endpoint, SQL query awal, dan parameter request di file payload.

2. Generate Scaffold

npx restforge-cli processor --project=mini-inventory --endpoint=stock-inbound-process --payload=stock_inbound_process.json

3. Tulis Business Logic

Buka file processor di src/modules/mini-inventory/processor/stock-inbound-process/confirm-inbound.js dan tulis business logic.

4. Regenerasi Aman (Safe Regeneration)

Jika payload berubah (misalnya menambah processor baru), generator dapat dijalankan ulang. File processor yang sudah di-customize tidak akan tertimpa:

# Aman: processor file yang ada tidak di-overwrite
npx restforge-cli processor --project=mini-inventory --endpoint=stock-inbound-process --payload=stock_inbound_process.json

Langkah Selanjutnya (Next Steps)

  • Component Engine untuk event lifecycle hooks pada operasi CRUD
  • Query Builder untuk membangun query SQL secara programatik di dalam processor
  • Cache untuk mengaktifkan caching pada processor

On this page