Cara Desain REST API untuk Integrasi ERP Manufaktur
Cara desain REST API untuk integrasi ERP manufaktur adalah tantangan yang sering dihadapi developer di industri. Sistem ERP biasanya punya arsitektur monolitik dan format data yang kompleks. Menghubungkannya dengan aplikasi modern membutuhkan API layer yang dirancang dengan baik. Artikel ini membahas langkah-langkah praktis membangun API tersebut menggunakan Laravel.
Mengapa REST API Penting untuk ERP Manufaktur
Sistem ERP di manufaktur mengelola data kritis: production order, bill of materials (BOM), inventory, quality control, hingga shipment tracking. Tanpa API yang terstruktur, integrasi biasanya dilakukan lewat direct database query atau file CSV - keduanya rawan error dan sulit di-maintain.
REST API memberikan beberapa keuntungan:
- Standarisasi - semua sistem bicara dalam format JSON yang konsisten
- Keamanan - autentikasi dan otorisasi terpusat
- Versioning - perubahan tidak langsung merusak consumer yang sudah jalan
- Monitoring - setiap request bisa di-log dan dianalisis
Menentukan Resource dan Endpoint
Langkah pertama adalah memetakan entitas ERP ke REST resource. Jangan langsung mapping 1:1 dari tabel database. Pikirkan dari sudut pandang consumer API.
Contoh Resource Utama
GET /api/v1/production-orders # Daftar production order
POST /api/v1/production-orders # Buat PO baru
GET /api/v1/production-orders/{id} # Detail satu PO
PATCH /api/v1/production-orders/{id} # Update status PO
GET /api/v1/materials # Master material
GET /api/v1/materials/{id}/stock # Stok material per lokasi
GET /api/v1/bom/{product_id} # Bill of Materials
POST /api/v1/quality-checks # Catat hasil QC
Tips Penamaan
- Gunakan plural noun untuk resource (
production-orders, bukanproduction-order) - Hindari verb di URL - gunakan HTTP method yang tepat
- Nested resource maksimal 2 level (
/materials/{id}/stock✓, bukan/warehouse/{id}/materials/{id}/stock/history)
Struktur Response yang Konsisten
Satu hal yang sering diabaikan: format response harus konsisten di seluruh endpoint. Buat sebuah base response structure.
Laravel API Resource
// app/Http/Resources/ProductionOrderResource.php
class ProductionOrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'order_number' => $this->order_number,
'product' => [
'id' => $this->product_id,
'name' => $this->product->name,
'sku' => $this->product->sku,
],
'quantity' => $this->quantity,
'status' => $this->status,
'start_date' => $this->start_date->format('Y-m-d'),
'due_date' => $this->due_date->format('Y-m-d'),
'created_at' => $this->created_at->toISOString(),
];
}
}
Envelope Response
Bungkus semua response dalam envelope yang seragam:
// app/Traits/ApiResponse.php
trait ApiResponse
{
protected function success($data, string $message = 'OK', int $code = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $code);
}
protected function error(string $message, int $code = 400, $errors = null)
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors,
], $code);
}
}
Dengan pattern ini, consumer API selalu tahu response structure-nya. Tidak perlu menebak format error vs sukses.
Autentikasi dan Otorisasi
Untuk integrasi ERP, biasanya ada dua jenis consumer: sistem internal (machine-to-machine) dan user dashboard. Gunakan strategi autentikasi yang sesuai.
Machine-to-Machine: API Key + HMAC
// app/Http/Middleware/VerifyApiSignature.php
class VerifyApiSignature
{
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-Key');
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Timestamp');
$client = ApiClient::where('api_key', $apiKey)->first();
if (!$client) {
return response()->json(['error' => 'Invalid API key'], 401);
}
// Cek timestamp tidak lebih dari 5 menit
if (abs(time() - (int)$timestamp) > 300) {
return response()->json(['error' => 'Request expired'], 401);
}
$payload = $timestamp . $request->getContent();
$expected = hash_hmac('sha256', $payload, $client->secret);
if (!hash_equals($expected, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$request->merge(['api_client' => $client]);
return $next($request);
}
}
User Dashboard: Laravel Sanctum
Untuk akses via dashboard atau mobile app, Sanctum sudah cukup:
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('production-orders', ProductionOrderController::class);
});
Validasi Data dari Sistem ERP
Data dari ERP sering tidak bersih. Field kosong, format tanggal campur-campur, angka dikirim sebagai string. Validasi ketat di API layer adalah kunci.
// app/Http/Requests/StoreProductionOrderRequest.php
class StoreProductionOrderRequest extends FormRequest
{
public function rules(): array
{
return [
'order_number' => ['required', 'string', 'max:50', 'unique:production_orders'],
'product_id' => ['required', 'exists:products,id'],
'quantity' => ['required', 'integer', 'min:1'],
'due_date' => ['required', 'date', 'after:today'],
'priority' => ['sometimes', 'in:low,medium,high,urgent'],
'notes' => ['nullable', 'string', 'max:1000'],
];
}
public function messages(): array
{
return [
'order_number.unique' => 'Nomor order sudah ada di sistem.',
'due_date.after' => 'Tanggal jatuh tempo harus di masa depan.',
];
}
}
Jangan percaya data dari sistem manapun - selalu validasi di trust boundary.
Handling Sync dan Idempotency
Integrasi ERP sering melibatkan sinkronisasi data secara berkala. Network bisa putus, request bisa duplikat. API harus idempotent.
Pattern: Upsert dengan External ID
// app/Http/Controllers/MaterialSyncController.php
public function sync(Request $request)
{
$validated = $request->validate([
'materials' => ['required', 'array', 'max:500'],
'materials.*.erp_code' => ['required', 'string'],
'materials.*.name' => ['required', 'string'],
'materials.*.unit' => ['required', 'string'],
'materials.*.stock' => ['required', 'numeric', 'min:0'],
]);
$results = collect($validated['materials'])->map(function ($item) {
return Material::updateOrCreate(
['erp_code' => $item['erp_code']],
[
'name' => $item['name'],
'unit' => $item['unit'],
'current_stock' => $item['stock'],
'last_synced_at' => now(),
]
);
});
return $this->success([
'synced' => $results->count(),
'timestamp' => now()->toISOString(),
]);
}
Dengan updateOrCreate berdasarkan erp_code, request yang sama bisa dikirim berulang tanpa membuat data duplikat.
Rate Limiting dan Pagination
Rate Limiting
Sistem ERP kadang mengirim burst data saat batch process. Atur rate limit yang masuk akal:
// routes/api.php
Route::middleware(['api.signature', 'throttle:erp'])->group(function () {
Route::post('/sync/materials', [MaterialSyncController::class, 'sync']);
});
// app/Providers/AppServiceProvider.php
RateLimiter::for('erp', function (Request $request) {
return Limit::perMinute(60)->by($request->header('X-API-Key'));
});
Cursor Pagination
Untuk endpoint list dengan data besar, gunakan cursor pagination - lebih efisien dari offset pagination untuk dataset yang sering berubah:
public function index(Request $request)
{
$orders = ProductionOrder::with('product')
->orderBy('id')
->cursorPaginate(50);
return ProductionOrderResource::collection($orders);
}
Logging dan Monitoring
Setiap API call dari/ke ERP harus tercatat. Ini bukan optional - ini kebutuhan audit di manufaktur.
// app/Http/Middleware/LogApiRequest.php
class LogApiRequest
{
public function handle(Request $request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$duration = round((microtime(true) - $start) * 1000);
Log::channel('api')->info('API Request', [
'method' => $request->method(),
'path' => $request->path(),
'client' => $request->header('X-API-Key'),
'status' => $response->status(),
'duration_ms' => $duration,
'ip' => $request->ip(),
]);
return $response;
}
}
Versioning Strategy
ERP berubah pelan, tapi tetap berubah. Gunakan URL versioning yang sederhana:
// routes/api.php
Route::prefix('v1')->group(function () {
Route::apiResource('production-orders', V1\ProductionOrderController::class);
});
Route::prefix('v2')->group(function () {
Route::apiResource('production-orders', V2\ProductionOrderController::class);
});
Jangan over-engineering versioning. Dua versi paralel sudah cukup. Deprecate v1 setelah semua consumer migrasi.
Kesimpulan
Mendesain REST API untuk integrasi ERP manufaktur membutuhkan perhatian ekstra di beberapa area: konsistensi response, validasi ketat, idempotency, dan logging. Laravel menyediakan semua building block yang diperlukan - dari API Resource, Form Request, middleware, hingga rate limiting.
Beberapa poin penting yang perlu diingat:
- Desain resource dari perspektif consumer, bukan dari struktur database ERP
- Validasi semua input - data dari ERP tidak selalu bersih
- Buat endpoint idempotent - network failure pasti terjadi
- Log setiap request - audit trail adalah keharusan di manufaktur
- Versioning sederhana - jangan over-engineering
Dengan fondasi API yang solid, integrasi antara sistem ERP legacy dan aplikasi modern menjadi lebih mudah di-maintain dan di-scale seiring pertumbuhan bisnis.