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)
| Properti | Nilai |
|---|---|
| Perintah generator | npx restforge-cli processor --project={project} --endpoint={endpoint} --payload={payload}.json |
| Lokasi processor | src/modules/{project}/processor/{endpoint}/{name}.js |
| Lokasi router | src/modules/{project}/{endpoint}.js |
| Processor signature | async process(input, services, req) |
| Services | { db, logger, redis, kafka, cache } |
| Overwrite-safe | File processor tidak di-overwrite (kecuali --force) |
| Database | PostgreSQL, MySQL, Oracle |
Perbedaan dengan CRUD Module (Comparison with CRUD Module)
| Aspek | CRUD Module | Processor |
|---|---|---|
| Tujuan | Operasi CRUD standar (datatables, create, update, delete, get) | Endpoint dengan business logic custom |
| Logic | Sepenuhnya di-generate dari payload | Scaffold awal di-generate, logic ditulis manual |
| Regenerasi | Aman di-regenerate kapan saja | File processor tidak di-overwrite jika sudah ada |
| Contoh | Manajemen data supplier, product, warehouse | Konfirmasi 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.jsonsrc/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, danquery - 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, danreqdari 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 ResponsePayload Processor (Processor Payload)
Payload processor mendefinisikan endpoint, HTTP method, SQL query awal, dan parameter request.
{
"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)
| Properti | Wajib | Keterangan |
|---|---|---|
version | Ya | Versi payload, gunakan "2.0" |
description | Tidak | Deskripsi umum endpoint group |
processor[].name | Ya | Nama processor, menjadi nama file dan segmen URL |
processor[].method | Ya | HTTP method: GET, POST, PUT, DELETE |
processor[].description | Tidak | Deskripsi processor |
processor[].sql.query | Ya | SQL query awal (scaffold) |
processor[].sql.params | Ya | Array nama parameter untuk query |
processor[].sql.file | Tidak | Alternatif: path ke file .sql eksternal |
processor[].request.body | Tidak | Definisi field dari request body |
processor[].request.params | Tidak | Definisi URL params (menjadi /:param di route) |
processor[].request.headers | Tidak | Definisi headers dengan mapTo untuk mapping ke input |
processor[].response.message | Tidak | Pesan 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
| Parameter | Tipe | Keterangan |
|---|---|---|
input | object | Gabungan dari req.body, req.params, dan req.query |
services | object | Injected services: { db, logger, redis, kafka, cache } |
req | object | Express request object untuk akses ke headers, ip, method, originalUrl |
Services yang Tersedia (Available Services)
| Service | Key | Keterangan |
|---|---|---|
| Database | db | { executeQuery, executeTransaction, getPool }, auto-route berdasarkan DB_TYPE |
| Logger | logger | pino logger instance |
| Redis | redis | Redis client (null jika tidak dikonfigurasi) |
| Kafka | kafka | Kafka service (null jika KAFKA_ENABLED bukan 'true') |
| Cache | cache | Cache 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
| Properti | Wajib | Default | Keterangan |
|---|---|---|---|
success | Ya | - | true atau false |
statusCode | Tidak | 200 | HTTP status code (200, 400, 404, 500, dll.) |
message | Ya | - | Pesan response |
data | Tidak | - | Data response (object atau array) |
timestamp | Tidak | - | Timestamp ISO 8601 |
Contoh Penggunaan (Usage Examples)
Query Database Sederhana
Processor untuk mengambil data stock inbound berdasarkan nomor inbound:
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:
{
"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:
{
"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.jsonStruktur 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 → cancelledRoute yang dihasilkan:
| Method | URL | Fungsi |
|---|---|---|
POST | /api/mini-inventory/stock-inbound-process/confirm-inbound | Konfirmasi stock inbound |
POST | /api/mini-inventory/stock-inbound-process/cancel-inbound | Batalkan 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 globalAturan Penamaan
| Komponen | Jenis Kata | Format | Contoh |
|---|---|---|---|
--project | Kata benda, domain | kebab-case | mini-inventory, sales, hr |
--endpoint | Kata benda, resource | kebab-case | stock-inbound-process, employee |
processor[].name | Kata kerja, aksi | kebab-case | confirm-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 ProcessorKedua 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.jsSuffix -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}.jsonParameter
| Parameter | Wajib | Keterangan |
|---|---|---|
--project | Ya | Domain/modul bisnis (contoh: mini-inventory) |
--endpoint | Ya | Nama resource group (contoh: stock-inbound-process) |
--payload | Ya | Nama file payload di folder payload/ |
--force | Tidak | Force overwrite processor file yang sudah ada |
Perilaku Generator (Generator Behavior)
| File | Sudah Ada? | Perilaku |
|---|---|---|
Router ({endpoint}.js) | Ya | Selalu di-overwrite |
Router ({endpoint}.js) | Tidak | Dibuat baru |
Processor (processor/{endpoint}/{name}.js) | Ya | Di-skip (tidak di-overwrite) |
Processor (processor/{endpoint}/{name}.js) | Tidak | Dibuat baru |
Processor (dengan --force) | Ya | Di-overwrite (file lama di-archive) |
Alur Kerja yang Disarankan (Recommended Workflow)
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.json3. 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.jsonLangkah 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