Advanced 40 min read

Project: External API Integration Component

By the end of this lesson, you'll be able to:

  • Apply Module 17's full integration architecture to a new realistic scenario
  • Reuse the Named Credential/Service/error-handling pattern for a different external API
  • Provide a robust, loading-state-aware client experience for an external lookup

Prerequisites: "Project: Document Upload Component"

What We're Building

A shipmentTracker component looking up a shipment's status from an external carrier API, reusing Module 17's exact architecture: Named Credential → Service → Controller → component, with full error/timeout handling.

The Apex Layer

public inherited sharing class ShipmentTrackingService {
    public static ShipmentStatus track(String trackingNumber) {
        HttpRequest request = new HttpRequest();
        request.setEndpoint('callout:Carrier_API/track/' + EncodingUtil.urlEncode(trackingNumber, 'UTF-8'));
        request.setMethod('GET');

        HttpResponse response;
        try {
            response = new Http().send(request);
        } catch (CalloutException e) {
            throw new AuraHandledException('Could not reach the carrier service. Please try again.');
        }

        if (response.getStatusCode() != 200) {
            throw new AuraHandledException('Tracking number not found.');
        }

        CarrierResponse raw = (CarrierResponse) JSON.deserialize(response.getBody(), CarrierResponse.class);
        return new ShipmentStatus(raw.status, raw.estimatedDelivery);
    }
}

public with sharing class ShipmentTrackingController {
    @AuraEnabled
    public static ShipmentStatus track(String trackingNumber) {
        return ShipmentTrackingService.track(trackingNumber);
    }
}

Every piece — the Named Credential reference, CalloutException/status-code handling, the wrapper class — is the identical pattern from Module 17's currency converter, applied to a completely different external system.

The Component

async handleTrack() {
    this.isTracking = true;
    try {
        this.shipmentStatus = await track({ trackingNumber: this.trackingNumber });
    } catch (error) {
        this.dispatchEvent(new ShowToastEvent({
            title: 'Tracking Failed',
            message: error.body?.message ?? 'An unexpected error occurred.',
            variant: 'error',
        }));
    } finally {
        this.isTracking = false;
    }
}

Called imperatively — triggered by a specific "Track" button click, not something that should load automatically — exactly Module 7's decision table applied once more.

Exercise

Explain, as a comment, what would need to change to reuse this same architecture for a completely different external API (e.g. a weather service).

Show hint

Think about what genuinely stays the same versus what's specific to this one integration.

APEX

Exercise

Challenge: why is track() called imperatively rather than via @wire?

Show hint

Recall Module 7's decision table.

JAVASCRIPT

Project: External API Integration Component Quiz

1. What architecture does this project reuse from Module 17?

2. What catches network-level failures like a timeout in this project?

3. What changes when reusing this architecture for a different external API?

4. What handles a non-200 response from the carrier API?

5. Why is track() called imperatively?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A shipment-tracking lookup applying Module 17's entire integration pattern to a new scenario — proof the architecture generalizes to any external REST API, not just the one worked example.