Skip to main content

Integration Tutorial

If you're building a Node.JS application like Next.JS or a GraphQL Server and need to integrate directly with HL7 interfaces, we've included messengers to make creating and sending HL7 simple once and for all.

Setup

To integrate Gofer Engine into an existing application, it seems silly to say, but you will need an existing application. Perhaps you are researching before building a new application or extending functionality of an existing one. Either way, this short tutorial purposely doesn't cover the setup of an existing project. But it does require that the project is a Node.JS environment, and you have access to the npm repository.

Another point to clarify is that integrating messenger services into your application should be implemented on the server side.

For example, in your Next.JS application, the messenger should be implemented in an API Page or Server Side function and not in a front-end page directly.

Install

Within your project, run:

npm i @gofer-engine/engine @gofer-engine/hl7 @gofer-engine/tools

Alternatively, use your package manager of choice and follow its syntax for adding new packages from the npm repository.

You may need additional/fewer packages from @gofer-engine/ depending on what functionality you need.

Create a Messenger

The gofer class exposes a method called messenger that accepts a MessengerRoute function. This method returns a tuple that contains the messenger callback and an id to identify the messenger class to observe events or control later in your application.

The MessengerRoute function follows the below simplified type. Also including the simplified ORoute interface for quick reference.

@gofer-engine/engine/src/types.ts
type MessengerRoute = (R: ORoute) => ORoute;

export interface ORoute extends OBase<ORoute> {
name: (name: string) => ORoute;
id: (id: string | number) => ORoute;
filter: (f: FilterFunc) => ORoute;
transform: (t: TransformFunc) => ORoute;
store: (s: StoreConfig) => ORoute;
setVar: (scope: varTypes, varName: string, varValue: MsgVar) => ORoute;
getVar: (scope: varTypes, varName: string, getVal: WithVarDo) => ORoute;
setMsgVar: (varName: string, varValue: MsgVar) => ORoute;
getMsgVar: (varName: string, getVal: WithVarDo) => ORoute;
setChannelVar: (varName: string, varValue: MsgVar) => ORoute;
getChannelVar: (varName: string, getVal: WithVarDo) => ORoute;
setGlobalVar: (varName: string, varValue: MsgVar) => ORoute;
getGlobalVar: (varName: string, getVal: WithVarDo) => ORoute;
setRouteVar: (varName: string, varValue: MsgVar) => ORoute;
getRouteVar: (varName: string, getVal: WithVarDo) => ORoute;
send(method: 'tcp', host: FunctProp<string>, port: FunctProp<number>): ORoute;
send(method: 'http', options: IHTTPConfig<true>): ORoute;
send(method: 'https', options: IHTTPSConfig<true>): ORoute;
}

For this tutorial, we will just use a single flow in the route to send to a tcp HL7 listener. The full example will show that the host and port options can come from the environment variables.

function/sample-messenger-function.ts
import gofer from '@gofer-engine/engine';

const [messenger] = gofer.messenger((route) =>
route.send('tcp', EHR_HL7_IP, EHR_HL7_A20_PORT),
);

Use the Callback

The messenger callback function accepts a function that is run to build the message to send to the route defined in the previous steps. This inline function is expected to return any one of the classes that implement the IMsg interface.

In this example, we will be using the HL7v2Msg class.

function/sample-messenger-function.ts
import HL7v2Msg from '@gofer-engine/hl7';

// ...

messenger((msg: HL7v2Msg) => {
return msg
.set('MSH-3', 'Gofer Engine')
.set('MSH-4', 'Bed Board App')
.set('MSH-5', 'ACME Hospital')
.set('MSH-7', now)
.set('MSH-9', 'ADT^A20^ADT_A20')
.set('MSH-10', genId('UID'))
.set('MSH-11-1', 'T')
.set('MSH-11-2', 'T')
.set('MSH-12', '2.4')
.addSegment('EVN')
.set('EVN-1', now)
.set('EVN-5', operator)
.addSegment(['NPU', bed, status.toString()]);
});

Alternatively, we could create and return a new instance of the HL7v2Msg class instead of using the argument of this callback function, which provides the interface IMsg. We asserted that msg is the HL7v2Msg default class. If we did not need custom methods such as the addSegment method, then we would not need to assert the class and could use any of the interface methods exposed by every message type class.

Full Example

Here is the full detailed example of using Gofer Engine to send ADT_A20 messages within a Next.JS API page.

function/sample-messenger-function.ts
import gofer from '@gofer-engine/engine';
import HL7v2Msg from '@gofer-engine/hl7';
import { genId } from '@gofer-engine/tools';

const EHR_HL7_IP = process.env['EHR_HL7_IP'];
const EHR_HL7_A20_PORT = parseInt(process.env['EHR_HL7_A20_PORT']);

const [messenger] = gofer.messenger((route) =>
route.send('tcp', EHR_HL7_IP, EHR_HL7_A20_PORT),
);

const sendBedStatusUpdate = async (
bed: string,
status: 1 | 2,
operator: string,
) => {
const now = new Date().toISOString().replace(/-|:|T/g, '').slice(0, 12);
return messenger((msg: HL7v2Msg) => {
return msg
.set('MSH-3', 'Gofer Engine')
.set('MSH-4', 'Bed Board App')
.set('MSH-5', 'ACME Hospital')
.set('MSH-7', now)
.set('MSH-9', 'ADT^A20^ADT_A20')
.set('MSH-10', genId('UID'))
.set('MSH-11-1', 'T')
.set('MSH-11-2', 'T')
.set('MSH-12', '2.4')
.addSegment('EVN')
.set('EVN-1', now)
.set('EVN-5', operator)
.addSegment(['NPU', bed, status.toString()]);
});
};

export default sendBedStatusUpdate;
pages/api/sample-next-api-page.ts
import sendBedStatusUpdate from './sample-messenger-function';
import type { NextApiRequest, NextApiResponse } from 'next';

const table0116_cust = {
Cleaning: 1,
Clean: 2,
} as const;

interface BedStatusUpdateRequest extends NextApiRequest {
body: {
bed: string;
status: keyof typeof table0116_cust;
operator: string;
};
}

const table0008 = {
AA: 'Accepted',
AE: 'Errored',
AR: 'Rejected',
CA: 'Commit Accepted',
CE: 'Commit Errored',
CR: 'Commit Rejected',
} as const;

type ResponseData = {
ack: 'Unknown' | (typeof table0008)[keyof typeof table0008];
};

const handler = async (
req: BedStatusUpdateRequest,
res: NextApiResponse<ResponseData>,
) => {
const { method } = req;

if (method !== 'POST') {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${method} Not Allowed`);
return;
}

const { bed, status, operator } = await req.json();
const mappedStatus = table0116_cust?.[status];

// TODO: validate input

const ackMsg = await sendBedStatusUpdate(bed, mappedStatus, operator);
const ack = table008?.[ackMsg.get('MSA-1')] || 'Unknown';
const message = ackMsg.get('MSA-3');

res.status(200).json({ ack, message });
};

export default handler;