Packages
hl7v2
2.3.0
3.10.1
3.9.0
3.8.0
3.7.0
3.6.0
3.5.0
3.4.0
3.3.6
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
2.11.0
2.10.0
2.9.1
2.9.0
2.8.2
2.8.1
2.8.0
2.7.1
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
1.4.6
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.6.0
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.1.0
Pure Elixir HL7 v2.x toolkit — schema-driven parsing, typed segments, message builder, MLLP transport
Current section
Files
Jump to
Current section
Files
guides/getting-started.md
# Getting Started
## Installation
Add `hl7v2` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:hl7v2, "~> 2.3"}
]
end
```
Then run `mix deps.get`.
## Parsing
HL7v2 supports two parsing modes: **raw** (canonical round-trip, delimiter-based) and **typed** (structs with named fields).
### Raw mode
```elixir
text = "MSH|^~\\&|HIS|HOSP|PACS|IMG|20260322||ADT^A01|MSG001|P|2.5\rPID|1||12345^^^MRN||Smith^John||19800315|M"
{:ok, raw} = HL7v2.parse(text)
raw.type # => {"ADT", "A01"}
raw.segments # => [{"MSH", [...]}, {"PID", [...]}]
```
### Typed mode
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed)
msh = hd(typed.segments)
msh.sending_application.namespace_id # => "HIS"
msh.message_type.message_code # => "ADT"
pid = Enum.find(typed.segments, &is_struct(&1, HL7v2.Segment.PID))
hd(pid.patient_name).given_name # => "John"
```
### Parse + validate in one step
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed, validate: true)
```
## Building Messages
Build HL7v2 messages programmatically with typed structs:
```elixir
alias HL7v2.Segment.{PID, EVN, PV1}
alias HL7v2.Type.{CX, XPN, FN, PL}
msg =
HL7v2.new("ADT", "A01",
sending_application: "MyApp",
sending_facility: "Hospital",
receiving_application: "PACS",
receiving_facility: "IMG"
)
|> HL7v2.Message.add_segment(%EVN{
event_type_code: "A01"
})
|> HL7v2.Message.add_segment(%PID{
patient_identifier_list: [%CX{id: "12345"}],
patient_name: [%XPN{family_name: %FN{surname: "Smith"}, given_name: "John"}]
})
|> HL7v2.Message.add_segment(%PV1{
patient_class: "I",
assigned_patient_location: %PL{point_of_care: "ICU", room: "101"}
})
wire = HL7v2.encode(msg)
```
## Acknowledgments (ACK/NAK)
Build ACK responses from the original message's MSH segment:
```elixir
# Parse the incoming message
{:ok, typed} = HL7v2.parse(incoming_wire, mode: :typed)
original_msh = hd(typed.segments)
# Accept
{ack_msh, msa} = HL7v2.Ack.accept(original_msh)
accept_wire = HL7v2.Ack.encode({ack_msh, msa})
# Reject with error details
{ack_msh, msa, err} = HL7v2.Ack.reject(original_msh,
error_code: "207",
error_text: "Application internal error",
text: "Could not process message"
)
reject_wire = HL7v2.Ack.encode({ack_msh, msa, err})
```
The shortcut `HL7v2.ack/2` is equivalent to `HL7v2.Ack.accept/2`:
```elixir
{ack_msh, msa} = HL7v2.ack(original_msh)
```
## Validation
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed)
case HL7v2.validate(typed) do
:ok ->
# Message is valid
:ok
{:error, errors} ->
# errors is a list of maps with :level, :location, :field, :message
for err <- errors do
IO.puts("#{err.level} in #{err.location}: #{err.message}")
end
end
```
## MLLP Transport
HL7v2 includes an integrated MLLP transport layer built on Ranch 2.x.
### Server
Define a handler module implementing the `HL7v2.MLLP.Handler` behaviour:
```elixir
defmodule MyHandler do
@behaviour HL7v2.MLLP.Handler
@impl true
def handle_message(message, _meta) do
case HL7v2.parse(message, mode: :typed) do
{:ok, typed} ->
msh = hd(typed.segments)
{ack_msh, msa} = HL7v2.Ack.accept(msh)
{:ok, HL7v2.Ack.encode({ack_msh, msa})}
{:error, _reason} ->
{:error, :parse_failed}
end
end
end
```
Start the listener:
```elixir
{:ok, _pid} = HL7v2.MLLP.Listener.start_link(
port: 2575,
handler: MyHandler
)
```
### Client
```elixir
{:ok, client} = HL7v2.MLLP.Client.start_link(host: "localhost", port: 2575)
{:ok, ack} = HL7v2.MLLP.Client.send_message(client, wire)
:ok = HL7v2.MLLP.Client.close(client)
```
### TLS
Both listener and client support TLS:
```elixir
# Server with TLS
{:ok, _} = HL7v2.MLLP.Listener.start_link(
port: 2576,
handler: MyHandler,
tls: [certfile: "server.pem", keyfile: "server-key.pem", cacertfile: "ca.pem"]
)
# Client with TLS
{:ok, client} = HL7v2.MLLP.Client.start_link(
host: "remote.host",
port: 2576,
tls: [verify: :verify_peer, cacertfile: "ca.pem"]
)
```
## Working with Unknown Segments
Real-world HL7 messages contain segments the library doesn't have typed definitions for.
These are preserved losslessly — you never lose data:
```elixir
{:ok, msg} = HL7v2.parse(text, mode: :typed)
Enum.each(msg.segments, fn
%HL7v2.Segment.PID{} = pid ->
# Typed — access fields by name
IO.inspect(pid.patient_name)
%HL7v2.Segment.ZXX{segment_id: id, raw_fields: fields} ->
# Z-segment — preserved with original segment ID
IO.puts("Z-segment #{id}: #{inspect(fields)}")
{name, raw_fields} ->
# Unknown segment — preserved as raw tuple
IO.puts("Unknown #{name}: #{length(raw_fields)} fields")
end)
# Path access works on all forms:
HL7v2.get(msg, "PID-5") # typed struct field
HL7v2.get(msg, "ZPD-1") # ZXX raw field by position
HL7v2.get(msg, "PR1-3") # raw tuple field by position
```
All forms encode back to valid wire format with `HL7v2.encode/1`.
## Next Steps
- Browse the [API reference](https://hexdocs.pm/hl7v2) for full module documentation
- See `HL7v2.Segment` modules for available segment types and their fields
- See `HL7v2.Type` modules for composite data type structs