Skip to content
Real-World Usage

Real-World Usage

This page demonstrates production-ready patterns for using gofhir/models in real applications. All examples use the R4 package.

Helper Function

func ptrTo[T any](v T) *T {
    return &v
}

1. FHIR Server Resource Creation Endpoint

An HTTP handler that accepts any FHIR resource, validates it, assigns an ID, and stores it:

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"

    "github.com/google/uuid"
    "github.com/gofhir/models/r4/v2"
)

func handleCreateResource(w http.ResponseWriter, req *http.Request) {
    if req.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    body, err := io.ReadAll(req.Body)
    if err != nil {
        http.Error(w, "Failed to read request body", http.StatusBadRequest)
        return
    }

    // Peek at the resource type before full deserialization
    resourceType, err := r4.GetResourceType(body)
    if err != nil {
        http.Error(w, "Invalid FHIR resource: missing resourceType", http.StatusBadRequest)
        return
    }

    if !r4.IsKnownResourceType(resourceType) {
        http.Error(w, fmt.Sprintf("Unknown resource type: %s", resourceType), http.StatusBadRequest)
        return
    }

    // Deserialize into the correct Go struct
    resource, err := r4.UnmarshalResource(body)
    if err != nil {
        http.Error(w, fmt.Sprintf("Failed to parse %s: %v", resourceType, err), http.StatusBadRequest)
        return
    }

    // Assign a server-generated ID
    resource.SetId(uuid.New().String())

    // Set metadata
    resource.SetMeta(&r4.Meta{
        VersionId:   ptrTo("1"),
        LastUpdated: ptrTo("2024-06-15T12:00:00Z"),
    })

    // Store the resource (implementation-specific)
    if err := store.Save(resource); err != nil {
        http.Error(w, "Failed to store resource", http.StatusInternalServerError)
        return
    }

    // Return the created resource
    responseData, err := r4.Marshal(resource)
    if err != nil {
        http.Error(w, "Failed to serialize response", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/fhir+json")
    w.Header().Set("Location", fmt.Sprintf("/%s/%s", resourceType, *resource.GetId()))
    w.WriteHeader(http.StatusCreated)
    w.Write(responseData)
}

2. Deserializing a Bundle from an API Response

Parsing a search result Bundle from a FHIR server API and extracting individual resources:

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gofhir/models/r4/v2"
)

func fetchPatients(baseURL string) ([]*r4.Patient, error) {
    resp, err := http.Get(baseURL + "/Patient?_count=50")
    if err != nil {
        return nil, fmt.Errorf("request failed: %w", err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("failed to read response: %w", err)
    }

    // Deserialize as a Bundle
    resource, err := r4.UnmarshalResource(body)
    if err != nil {
        return nil, fmt.Errorf("failed to parse Bundle: %w", err)
    }

    bundle, ok := resource.(*r4.Bundle)
    if !ok {
        return nil, fmt.Errorf("expected Bundle, got %s", resource.GetResourceType())
    }

    // Extract Patient resources from Bundle entries
    var patients []*r4.Patient
    for _, entry := range bundle.Entry {
        if entry.Resource == nil {
            continue
        }
        if patient, ok := entry.Resource.(*r4.Patient); ok {
            patients = append(patients, patient)
        }
    }

    fmt.Printf("Found %d patients (total: %d)\n", len(patients), safeTotal(bundle.Total))
    return patients, nil
}

func safeTotal(t *uint32) uint32 {
    if t != nil {
        return *t
    }
    return 0
}

3. Converting Between JSON and XML Format

The library supports both FHIR JSON and FHIR XML. You can convert between formats by deserializing from one and serializing to the other:

import (
    "encoding/json"
    "fmt"

    "github.com/gofhir/models/r4/v2"
)

// JSON to XML conversion
func jsonToXML(jsonData []byte) ([]byte, error) {
    // Parse JSON into a Resource
    resource, err := r4.UnmarshalResource(jsonData)
    if err != nil {
        return nil, fmt.Errorf("failed to parse JSON: %w", err)
    }

    // Serialize to XML
    xmlData, err := r4.MarshalResourceXMLIndent(resource, "", "  ")
    if err != nil {
        return nil, fmt.Errorf("failed to marshal XML: %w", err)
    }

    return xmlData, nil
}

// XML to JSON conversion
func xmlToJSON(xmlData []byte) ([]byte, error) {
    // Parse XML into a Resource
    resource, err := r4.UnmarshalResourceXML(xmlData)
    if err != nil {
        return nil, fmt.Errorf("failed to parse XML: %w", err)
    }

    // Serialize to JSON (HTML-safe)
    jsonData, err := r4.MarshalIndent(resource, "", "  ")
    if err != nil {
        return nil, fmt.Errorf("failed to marshal JSON: %w", err)
    }

    return jsonData, nil
}

Usage example:

jsonInput := []byte(`{
    "resourceType": "Patient",
    "id": "example",
    "name": [{"family": "Smith", "given": ["John"]}]
}`)

xmlOutput, err := jsonToXML(jsonInput)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(xmlOutput))
// <?xml version="1.0" encoding="UTF-8"?>
// <Patient xmlns="http://hl7.org/fhir">
//   <id value="example"/>
//   <name>
//     <family value="Smith"/>
//     <given value="John"/>
//   </name>
// </Patient>

4. Type-Safe Resource Routing Using the Registry

A resource router that dispatches to type-specific handlers using the registry:

import (
    "fmt"
    "net/http"

    "github.com/gofhir/models/r4/v2"
)

type ResourceHandler func(r4.Resource) error

type FHIRRouter struct {
    handlers map[string]ResourceHandler
}

func NewFHIRRouter() *FHIRRouter {
    return &FHIRRouter{
        handlers: make(map[string]ResourceHandler),
    }
}

func (router *FHIRRouter) Handle(resourceType string, handler ResourceHandler) {
    if !r4.IsKnownResourceType(resourceType) {
        panic(fmt.Sprintf("unknown resource type: %s", resourceType))
    }
    router.handlers[resourceType] = handler
}

func (router *FHIRRouter) Dispatch(data []byte) error {
    resource, err := r4.UnmarshalResource(data)
    if err != nil {
        return fmt.Errorf("failed to unmarshal: %w", err)
    }

    handler, ok := router.handlers[resource.GetResourceType()]
    if !ok {
        return fmt.Errorf("no handler registered for %s", resource.GetResourceType())
    }

    return handler(resource)
}

Register handlers for specific resource types:

router := NewFHIRRouter()

router.Handle("Patient", func(res r4.Resource) error {
    patient := res.(*r4.Patient)
    fmt.Printf("Processing patient: %s\n", safeName(patient))
    // Validate, store, transform, etc.
    return nil
})

router.Handle("Observation", func(res r4.Resource) error {
    obs := res.(*r4.Observation)
    fmt.Printf("Processing observation: %s\n", safeCode(obs))
    // Process the observation
    return nil
})

// Dispatch incoming resources
err := router.Dispatch(incomingJSON)

Listing All Supported Resource Types

You can use AllResourceTypes to auto-register default handlers or generate API documentation:

import (
    "sort"
    "github.com/gofhir/models/r4/v2"
)

// Generate OpenAPI paths for all resource types
types := r4.AllResourceTypes()
sort.Strings(types)

for _, rt := range types {
    fmt.Printf("/%s:\n", rt)
    fmt.Printf("  get:\n    summary: Search %s resources\n", rt)
    fmt.Printf("  post:\n    summary: Create a new %s\n", rt)
}
The registry functions use an internal factory map that is populated at compile time. There is no runtime registration step and no initialization required beyond importing the package. All registry functions are safe for concurrent use.

Putting It All Together

A minimal FHIR facade that combines several patterns:

import (
    "fmt"
    "io"
    "net/http"

    "github.com/gofhir/models/r4/v2"
)

func main() {
    mux := http.NewServeMux()

    // Accept any FHIR resource via POST
    mux.HandleFunc("/fhir/", func(w http.ResponseWriter, req *http.Request) {
        body, _ := io.ReadAll(req.Body)

        // Parse content type to determine format
        contentType := req.Header.Get("Content-Type")

        var resource r4.Resource
        var err error

        switch contentType {
        case "application/fhir+xml":
            resource, err = r4.UnmarshalResourceXML(body)
        default:
            resource, err = r4.UnmarshalResource(body)
        }

        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        // Respond in the requested format
        accept := req.Header.Get("Accept")
        switch accept {
        case "application/fhir+xml":
            data, _ := r4.MarshalResourceXMLIndent(resource, "", "  ")
            w.Header().Set("Content-Type", "application/fhir+xml")
            w.Write(data)
        default:
            data, _ := r4.Marshal(resource)
            w.Header().Set("Content-Type", "application/fhir+json")
            w.Write(data)
        }
    })

    fmt.Println("FHIR server listening on :8080")
    http.ListenAndServe(":8080", mux)
}
Last updated on