Building Effective Bridging Middleware for Legacy SIMRS Systems and National Integration
T
Kembali ke Blog

Building Effective Bridging Middleware for Legacy SIMRS Systems and National Integration

Tutorial
Tim Pilar Inovasi 01 Sep 2026 16 min baca 3,575 kata 141
Integrating legacy hospital systems with modern platforms such as BPJS and SatuSehat is often challenging. This article guides you through building robust, efficient, and secure bridging middleware using current technologies to overcome these challenges and ensure critical data interoperability.

In the ever-evolving landscape of healthcare technology, the challenge of system integration is becoming increasingly complex. Many healthcare facilities, from large hospitals to private clinics, still rely on Hospital Management Information Systems (SIMRS) or Clinic Information Systems (SIM Klinik) that have been operating for years, even decades. These legacy systems, although stable, are often built with monolithic architectures and outdated technology, making it difficult to connect with modern platforms required by regulations, such as BPJS Kesehatan or the national health data integration platform SatuSehat from the Indonesian Ministry of Health, which adopts the FHIR standard. Without a strong bridge, vital patient and service data cannot flow smoothly, causing duplication of work, data inconsistencies, and significant obstacles to operational efficiency and service quality. This article will guide you in depth on how to build an effective, robust, and secure middleware bridging, using a practical approach and concrete code examples. We will discuss basic concepts, implementation details with technologies such as Laravel 11 and Node.js, and include examples of data payloads and best practices to ensure successful and sustainable integration.

Basic Concepts of Middleware Bridging for Legacy Systems

Middleware bridging is a software layer that serves as an intermediary between two or more applications or systems that are not directly compatible. Its purpose is to translate, reformat, and stream data between these systems, allowing them to communicate as if they were part of a single integrated ecosystem. In the context of legacy SIMRS or SIM Klinik, middleware bridging is crucial to overcome several fundamental problems. First, differences in communication protocols: legacy systems may use raw TCP/IP-based protocols, sockets, or even file-sharing, while modern platforms like SatuSehat exclusively rely on RESTful APIs with JSON data formats based on the FHIR R4 standard. Second, differences in data formats: old SIMRS may store data in highly specific relational database schemas, while external integration requires data in structured formats such as JSON, XML, or HL7 v2.x. Third, security and authentication issues: legacy systems often have outdated security mechanisms, whereas modern integration demands token-based authentication (e.g., OAuth 2.0) and end-to-end data encryption.

Imagine a SIMRS that has been operating since 2005, using the Delphi 7 programming language and PostgreSQL 9.x database. This system records patient data, medical records, and service transactions. Now, the hospital is required to report patient visit and diagnosis data to the SatuSehat platform, which requires data in FHIR R4 format. Middleware bridging will act as a translator. When a new patient is registered in the legacy SIMRS, the middleware will detect that event (for example through a database trigger or periodic polling), retrieve the patient data from PostgreSQL 9.x, map that data into the Patient FHIR R4 structure, perform validation, and then send it to the SatuSehat API using HTTPS and token-based authentication. A similar process applies to integration with BPJS Kesehatan, which may require data in XML or JSON format with a different schema.

It is important to understand that middleware bridging is not just 'copy-paste' of data. It involves complex business logic, including data transformation, data aggregation from various sources, schema validation, error handling, retry mechanisms, logging, and monitoring. The middleware architecture should ideally be designed as microservices or a set of separate services, enabling easier scalability and maintenance. Thus, when there is a change in one of the systems (for example, an update to the SatuSehat API), only the relevant part of the middleware needs to be changed, not the entire legacy system.

Implementation Details: Choosing Technology and Architecture

In building middleware bridging, choosing the right technology is key. We recommend a combination of modern technologies that are flexible and supported by a broad community. For the main backend that handles business logic, data transformation, and orchestration, PHP with the Laravel framework version 11.x is a very strong choice. Laravel offers ease of developing RESTful APIs, an intuitive Eloquent ORM for interacting with various databases (including PostgreSQL 16.x which is often used in legacy SIMRS), as well as Queue features that are essential for processing integration asynchronously and being resilient to failures. Another alternative is Node.js with the Express.js or NestJS framework, especially if you need very high I/O performance or real-time integration using WebSockets. For this example, we will focus on Laravel 11.x.

The database for the middleware itself should be separate from the legacy system database. PostgreSQL version 16.x is an excellent choice due to its stability, performance, and ability to handle both structured and semi-structured data (JSONB). This database will be used to store integration transaction logs, delivery status, mapping data, and message queues if we use a database driver for the queue. For more robust message queues, Redis 7.x or RabbitMQ 3.x are highly recommended, especially in production environments that require high scalability and guaranteed message delivery.

When it comes to integration standards, there are several that are dominant in the healthcare sector. For clinical data integration, the HL7 v2.5.1 standard is still widely used by medical devices and legacy systems, while FHIR R4 (Fast Healthcare Interoperability Resources Release 4) is the future standard adopted by SatuSehat. Your middleware must be able to map from one standard to another, or from a proprietary database schema to these standards. For FHIR parsing and validation, libraries such as HAPI FHIR (for Java) or fhir.js (for JavaScript) can serve as references. In PHP, we can use community-built libraries or build custom parsers/formatters.

The recommended architecture is based on an API Gateway and Microservices. The bridging middleware itself can be considered as one or several microservices that focus on specific integration tasks (for example, one service for BPJS integration, one for SatuSehat). The API Gateway (such as Nginx or Kong) will act as a single entry point, handling authentication, authorization, rate limiting, and routing requests to the appropriate microservices. This ensures centralized security and access management. Asynchronous processes using queues are essential for decoupling. When the legacy SIMRS triggers an event (for example, a new patient), the event does not directly call an external API. Instead, the event is placed into a queue, and a processor worker (Laravel Queue Daemon) will pick it up for processing, reducing the load on the SIMRS and increasing the system's resilience to network or external API failures.

Case Study: Middleware Implementation with Laravel 11 and Node.js

Let's look at a concrete implementation for integrating patient data from a legacy SIMRS to SatuSehat using Laravel 11.x for the backend API and Node.js for the worker service that processes the queue. Assume the legacy SIMRS has a pasien table with columns such as id_pasien, nama_lengkap, tanggal_lahir, jenis_kelamin, alamat.

First, we will create an API endpoint in Laravel that will receive notifications from the legacy SIMRS (for example, when a new patient is added or patient data is updated). This endpoint will validate the data and then send it to the queue for asynchronous processing.

// app/Http/Controllers/PasienWebhookController.php<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;use App\Jobs\ProcessPasienSatuSehat;use Illuminate\Support\Facades\Validator;use Illuminate\Validation\Rule;class PasienWebhookController extends Controller{    public function receivePasienUpdate(Request $request)    {        $validator = Validator::make($request->all(), [            'id_pasien_legacy' => 'required|string|max:50',            'nama_lengkap' => 'required|string|max:255',            'tanggal_lahir' => 'required|date_format:Y-m-d',            'jenis_kelamin' => ['required', Rule::in(['L', 'P'])],            'alamat' => 'required|string|max:500',            // ... validate other relevant columns        ]);        if ($validator->fails()) {            return response()->json(['message' => 'Invalid data provided', 'errors' => $validator->errors()], 400);        }        // Data is valid, send to the queue        ProcessPasienSatuSehat::dispatch($request->all());        return response()->json(['message' => 'Pasien data received and queued for processing'], 202);    }}

The code above defines a controller that receives patient data, validates it, and then sends it to a Job ProcessPasienSatuSehat. This Job is the key to asynchronous processing. With Laravel Queue, we can use a database, Redis, or RabbitMQ as the queue driver.

Next, we will create the Job ProcessPasienSatuSehat which is responsible for mapping the legacy patient data to the FHIR R4 format and sending it to the SatuSehat API. In this example, we will use Laravel's built-in HTTP Client to send the request to SatuSehat.

// app/Jobs/ProcessPasienSatuSehat.php<?phpnamespace App\Jobs;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Queue\SerializesModels;use Illuminate\Support\Facades\Http;use Illuminate\Support\Facades\Log;use Throwable;class ProcessPasienSatuSehat implements ShouldQueue{    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;    protected $pasienData;    public function __construct(array $pasienData)    {        $this->pasienData = $pasienData;    }    public function handle(): void    {        Log::info('Processing pasien data for SatuSehat', ['pasien_id_legacy' => $this->pasienData['id_pasien_legacy']]);        try {            // 1. Mapping data from legacy format to FHIR R4 Patient Resource            $fhirPatient = $this->mapToFhirPatient($this->pasienData);            // 2. Get Access Token for SatuSehat            $accessToken = $this->getSatuSehatAccessToken(); // Implement this function            // 3. Send data to SatuSehat API            $response = Http::withToken($accessToken)                             ->acceptJson()                             ->post(env('SATUSEHAT_API_BASE_URL') . '/Patient', $fhirPatient);            if ($response->successful()) {                Log::info('Pasien data successfully sent to SatuSehat', ['response' => $response->json()]);                // Save success status to middleware database            } else {                Log::error('Failed to send pasien data to SatuSehat', ['response' => $response->json(), 'status' => $response->status()]);                // Consider throwing an exception so the job is retried                throw new \Exception('SatuSehat API error: ' . $response->body());            }        } catch (Throwable $e) {            Log::error('Error processing pasien data for SatuSehat: ' . $e->getMessage(), ['pasien_id_legacy' => $this->pasienData['id_pasien_legacy']]);            // This will ensure the job is retried according to the queue configuration            $this->fail($e);        }    }    protected function mapToFhirPatient(array $data): array    {        // Simple mapping example. Adjust to the FHIR R4 specification and your SIMRS data.        $genderMap = ['L' => 'male', 'P' => 'female'];        return [            "resourceType" => "Patient",            "identifier" => [                [                    "system" => "http://sys-dev.com/simrs/patient-id", // Replace with your SIMRS identifier system                    "value" => $data['id_pasien_legacy']                ]            ],            "name" => [                [                    "use" => "official",                    "text" => $data['nama_lengkap']                ]            ],            "gender" => $genderMap[$data['jenis_kelamin']] ?? 'unknown',            "birthDate" => $data['tanggal_lahir'],            "address" => [                [                    "use" => "home",                    "text" => $data['alamat']                ]            ]            // ... add other FHIR elements        ];    }    protected function getSatuSehatAccessToken(): string    {        // Implementation to get access token from SatuSehat        // Usually involves client_credentials grant type to the /oauth2/v1/accesstoken endpoint        // Make sure the token is stored and cached to avoid repeated requests        // Example:        $response = Http::asForm()->post(env('SATUSEHAT_AUTH_URL') . '/oauth2/v1/accesstoken', [            'client_id' => env('SATUSEHAT_CLIENT_ID'),            'client_secret' => env('SATUSEHAT_CLIENT_SECRET'),            'grant_type' => 'client_credentials'        ]);        if ($response->successful()) {            return $response->json('access_token');        }        Log::error('Failed to get SatuSehat access token', ['response' => $response->json()]);        throw new \Exception('Could not obtain SatuSehat access token.');    }}

This second code shows how patient data from the legacy SIMRS is transformed into the FHIR R4 Patient resource format, then sent to the SatuSehat API. The mapToFhirPatient function is the core of the data bridging process, where you define how each column from the legacy system is mapped to the corresponding FHIR element. The getSatuSehatAccessToken function is an example of how you can manage authentication to the SatuSehat API, which typically uses OAuth 2.0. It is important to manage this token wisely, such as storing it in a cache and refreshing it before it expires. Error handling and the automatic retry mechanism by Laravel Queue are very helpful in maintaining system resilience.

Data and Error Handling: Example Payload and Retry Mechanism

One of the most important aspects of middleware bridging is the ability to handle various types of data and inevitable failures. Data originating from legacy systems often has inconsistencies, unexpected null values, or formats that do not conform to modern standards. On the other hand, external APIs such as SatuSehat or BPJS Kesehatan have strict validation schemas and can return various types of errors.

Let's look at an example of a valid FHIR R4 payload for the Patient resource, which we will send to SatuSehat. Note the structure and data types that conform to the FHIR R4 specification.

{  "resourceType": "Patient",  "identifier": [    {      "system": "http://sys-dev.com/simrs/patient-id",      "value": "PSN-0012345"    },    {      "system": "https://fhir.kemkes.go.id/id/nik",      "value": "3201010101000001"    }  ],  "name": [    {      "use": "official",      "text": "Budi Santoso",      "family": "Santoso",      "given": ["Budi"]    }  ],  "telecom": [    {      "system": "phone",      "value": "081234567890",      "use": "mobile"    },    {      "system": "email",      "value": "budi.santoso@example.com"    }  ],  "gender": "male",  "birthDate": "1985-05-15",  "address": [    {      "use": "home",      "type": "physical",      "text": "Jl. Merdeka No. 45, RT 001 RW 002, Kel. Sukamaju, Kec. Cisarua, Kab. Bandung, 40552",      "line": ["Jl. Merdeka No. 45"],      "city": "Bandung",      "postalCode": "40552",      "country": "ID"    }  ],  "maritalStatus": {    "coding": [      {        "system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",        "code": "M"      }    ],    "text": "Married"  },  "active": true}

The payload above is a complete example that includes several important elements of the Patient resource. Common errors occur if one of the required elements is missing, or the data format is incorrect (for example, birthDate is not in YYYY-MM-DD format).

When a failure occurs, the external API will return an HTTP status code and an error message. An example error from SatuSehat if validation fails:

{  "resourceType": "OperationOutcome",  "issue": [    {      "severity": "error",      "code": "invalid",      "details": {        "text": "The element 'Patient.birthDate' is required."      },      "expression": ["Patient.birthDate"]    }  ]}

To handle this error, the middleware must have a robust mechanism.

  1. Detailed Logging: Log every failure, including the payload sent, the error response received, and the timestamp. Use a centralized logging system such as the ELK Stack (Elasticsearch, Logstash, Kibana) or Graylog to facilitate analysis.
  2. Retry Mechanism: For temporary failures (for example, network issues, the external API is busy), the middleware must retry sending the data after a certain time interval. Laravel Queue by default supports retries. You can configure the number of attempts (tries) and the wait time between attempts (retry_after) in your Job. For example, public $tries = 3; public $backoff = 60; will try 3 times with a 60-second delay.
  3. Dead Letter Queue (DLQ): If after several attempts the data still fails to send, the data must be moved to the DLQ. This prevents failed jobs from continuously flooding the main queue and gives developers a chance to review and fix the problem manually. Data in the DLQ can be reprocessed after fixes are made.
  4. Alerting: Configure a monitoring system to send notifications (email, Slack, Telegram) to the IT team or developers when a job fails and enters the DLQ or when the error rate reaches a certain threshold.
  5. Data Reconciliation: After the error is fixed, you may need to perform data reconciliation. Ensure there is a mechanism to retrieve failed data from the DLQ, modify it, and send it again. This could involve a simple web interface on the middleware side or a dedicated CLI script.
  6. Idempotency: Design your middleware API so that operations are idempotent, meaning sending the same request multiple times will not produce unwanted side effects (for example, creating duplicate entities). This is very important for the retry mechanism.

Implementing this mechanism will greatly improve the resilience and reliability of your bridging middleware.

Best Practices in Building Bridging Middleware

Building effective bridging middleware requires more than just functional code. Here are the best practices you should apply to ensure long-term success and efficient maintenance:

  1. Design for Scalability and Resilience: Use a queue-based architecture (message queues such as Redis, RabbitMQ) for asynchronous processing and decoupling. Ensure the middleware can handle increased data volume without performance degradation and has automatic retry mechanisms as well as a Dead Letter Queue (DLQ) for failed jobs.
  2. Implement Idempotency: Ensure that every data-sending operation to the target system is idempotent. That is, sending the same data multiple times will not create duplication or unwanted side effects. This is crucial for retry mechanisms and recovery from failures.
  3. Comprehensive Logging and Monitoring: Implement detailed logging for every transaction, including the request payload, the response from the target system, and any errors that occur. Integrate with monitoring tools (e.g., Prometheus, Grafana, Sentry) to gain real-time visibility into the middleware's performance and health.
  4. Strict Data Security: Always use HTTPS/TLS for all communications. Ensure sensitive data is encrypted, both in transit and at rest (encryption at rest). Implement strong authentication and authorization (e.g., OAuth 2.0, API Keys with periodic rotation) for access to the middleware API and external APIs.
  5. Centralized Configuration Management: Avoid hardcoding configuration values such as API URLs, credentials, or other parameters. Use environment variables (.env), configuration management systems (e.g., HashiCorp Vault), or cloud services (AWS Secrets Manager, Azure Key Vault) to manage configuration securely and dynamically.
  6. Flexible and Documented Data Mapping: Create clear and detailed documentation on how each data element from the legacy system is mapped to the target standard (FHIR, HL7, BPJS schema). Use mapping tables or external configuration that is easy to update. Avoid implicit assumptions about data structures.
  7. Comprehensive Automated Testing: Write unit tests, integration tests, and end-to-end tests for every middleware component. This includes testing data validation, data transformation, API connectivity, and error handling. Test automation will help catch regressions and ensure stability when changes occur.
  8. API Versioning and Backward Compatibility: Anticipate changes to external APIs or legacy systems. Design the middleware with API versioning in mind (e.g., /v1/patient, /v2/patient) and strive to maintain backward compatibility for as long as possible. If changes are unavoidable, communicate proactively and provide a transition period.
  9. Clear API Documentation: If your middleware exposes an API for the legacy system, provide clear API documentation using standards such as OpenAPI (Swagger). This makes it easier for the legacy system development team to interact with your middleware.

FAQ: Common Questions About Middleware Bridging

  1. What is the main difference between middleware and ETL (Extract, Transform, Load)?

    Middleware focuses on real-time or near real-time communication between systems, enabling them to interact directly or asynchronously through message exchange. Meanwhile, ETL is more oriented toward batch data movement from one or more sources to a data warehouse or data lake for analysis, often with more complex and indirect transformations. Middleware is more dynamic and interactive, while ETL is more periodic and static for reporting purposes.

  2. How important is security in middleware bridging, especially in the healthcare sector?

    Security is the most crucial aspect in the healthcare sector. Patient data is highly sensitive information and is strictly regulated by regulations such as the Personal Data Protection Law in Indonesia or HIPAA in the US. Middleware must ensure end-to-end data encryption, strong authentication (e.g., OAuth 2.0), role-based authorization, and a complete audit trail. Security failures can lead to large fines, loss of trust, and legal issues.

  3. How can data changes in a legacy system be detected if there is no webhook mechanism?

    If the legacy system does not provide webhooks or event notifications, you can use several strategies. One is to 'poll' the database periodically for data changes (e.g., comparing updated_at or created_at timestamps). Another alternative is to use 'database triggers' directly in the legacy database to write change logs to a separate table that is then processed by the middleware, or even use CDC (Change Data Capture) tools if the infrastructure supports it.

  4. What are the biggest challenges in implementing middleware bridging?

    The biggest challenges include: (1) Understanding the complex and often poorly documented data schemas of legacy systems. (2) Handling data inconsistencies and poor data quality from legacy sources. (3) Maintaining performance and scalability as data volume increases. (4) Ensuring security and regulatory compliance. (5) Robust error handling and retry mechanisms. (6) Collaboration between teams (legacy developers and middleware developers) who often have different understandings of technology.

  5. Is middleware bridging always necessary, or are there other alternatives?

    Middleware bridging is highly recommended when you need to integrate legacy systems with modern systems in real-time or near real-time, especially if there are significant differences in protocols and data formats. Alternatives could include: (1) Total migration of the legacy system (often expensive and high-risk). (2) Direct point-to-point integration (creates spaghetti code and is difficult to manage as the number of integrations grows). (3) Using commercial ESB (Enterprise Service Bus) solutions that are more expensive and complex. Middleware bridging offers a balance between flexibility, cost, and control.

  6. How long does it take to build a complex middleware bridging?

    The development duration varies greatly depending on the complexity of the integration, the number of systems involved, the quality of legacy system documentation, and the availability of resources. For simple integration (one legacy system to one external API with a few data entities), it can take 2-4 months. For more complex integration with many entities, complex data transformation, and high-performance requirements, it can take 6-12 months or even more, including the analysis, design, development, testing, and deployment phases.

Building middleware bridging for legacy systems is not a trivial task, but it is a crucial strategic investment for the future of your healthcare facility. With the adoption of national standards such as SatuSehat and integration with BPJS, the ability to flow data efficiently and securely is no longer an option, but a necessity. By following this practical guide, from selecting modern technologies such as Laravel 11.x and PostgreSQL 16.x, implementing a queue-based architecture, to applying best practices in security and error handling, you can ensure robust data interoperability. This will significantly improve operational efficiency, support data-driven decision-making, and ultimately, improve the quality of healthcare services you provide. If you need expert assistance in designing, developing, or implementing a middleware bridging solution tailored to the specific needs of your SIMRS or SIM Klinik, do not hesitate to contact us. Our team is ready to help you navigate the complexities of system integration and realize digital transformation.

Related solutions by PT Pilar Inovasi Informatika (Pilar Inovasi IT products): SIMRS (Hospital Information System) BPJS & SatuSehat Integration. Need a free consultation?

Terakhir diperbarui 18 Oct 2026

Komentar

Komentar ditinjau sebelum tampil.

Belum ada komentar. Jadilah yang pertama!