DEV Community

Cover image for Build a Real-time Package Tracking API with Tinybird
Cameron Archer for Tinybird

Posted on

Build a Real-time Package Tracking API with Tinybird

Tracking packages across different carriers and understanding their delivery statuses in real-time can be a complex data engineering challenge. This tutorial will guide you through creating a real-time package tracking API that provides current status information, full delivery history, and carrier performance analytics. We'll use Tinybird, a data analytics backend for software developers. Tinybird helps you build real-time analytics APIs without the hassle of setting up or managing the underlying infrastructure. It leverages data sources and pipes to ingest, transform, and serve your data through fast, scalable APIs. By the end of this tutorial, you'll know how to ingest package tracking data, transform this data to derive meaningful insights, and publish APIs that allow real-time tracking of packages. Let's get started.

Understanding the data

Imagine your data looks like this:

{"package_id": "PKG-3556", "event_timestamp": "2025-05-06 05:45:57", "status": "Picked up", "location": "Miami, FL", "carrier": "UPS", "notes": "Package in transit to next facility", "updated_by": "agent_856"}
{"package_id": "PKG-2603", "event_timestamp": "2025-04-28 22:45:57", "status": "In transit", "location": "Houston, TX", "carrier": "DHL", "notes": "No additional notes", "updated_by": "agent_803"}
... ```
{% endraw %}


This data represents package delivery events from different carriers. Each event includes a unique package ID, timestamp of the event, status, location, carrier, notes on the event, and who updated the event. To store this data in Tinybird, we create a data source designed to efficiently query and manage the delivery events. Here's how you define the {% raw %}`package_events`{% endraw %} data source in Tinybird:
{% raw %}


```json
DESCRIPTION >
    Stores package delivery events with status updates

SCHEMA >
    `package_id` String `json:$.package_id`,
    `event_timestamp` DateTime `json:$.event_timestamp`,
    `status` String `json:$.status`,
    `location` String `json:$.location`,
    `carrier` String `json:$.carrier`,
    `notes` String `json:$.notes`,
    `updated_by` String `json:$.updated_by`

ENGINE "MergeTree"
ENGINE_PARTITION_KEY "toYYYYMM(event_timestamp)"
ENGINE_SORTING_KEY "package_id, event_timestamp"
Enter fullscreen mode Exit fullscreen mode

In this schema, we've chosen types and keys to optimize query performance. The ENGINE_SORTING_KEY on package_id and event_timestamp allows for efficient filtering and ordering of package events. To ingest data into this data source, Tinybird's Events API enables streaming of JSON/NDJSON events from your application frontend or backend with a simple HTTP request. It's designed for real-time, low latency ingestion. Here's how you can ingest data into the package_events data source:

curl -X POST "https://api.europe-west2.gcp.tinybird.co/v0/events?name=package_events&utm_source=DEV&utm_campaign=tb+create+--prompt+DEV" \
    -H "Authorization: Bearer $TB_ADMIN_TOKEN" \
    -d '{
        "package_id": "PKG12345678", 
        "event_timestamp": "2023-11-23 14:30:00", 
        "status": "in_transit", 
        "location": "Chicago Distribution Center", 
        "carrier": "UPS", 
        "notes": "Package processed at sorting facility", 
        "updated_by": "system"
    }'
Enter fullscreen mode Exit fullscreen mode

For event/streaming data, the Kafka connector is also an option that offers benefits like robust integration with existing Kafka pipelines. For batch or file data, the Data Sources API and S3 connector facilitate bulk ingestion.

Transforming data and publishing APIs

Tinybird transforms data through pipes, which can perform batch transformations or real-time transformations, and even publish these transformations as API Endpoints.

Real-time status and history APIs

Let's start with the package_status endpoint, which retrieves the most recent status of a package:

DESCRIPTION >
    Get the current status of a package by its ID

NODE package_status_node
SQL >
    SELECT 
        package_id,
        argMax(status, event_timestamp) as current_status,
        argMax(location, event_timestamp) as current_location,
        argMax(event_timestamp, event_timestamp) as last_updated,
        carrier
    FROM package_events
    WHERE package_id = {{String(package_id, '')}}
    GROUP BY package_id, carrier

TYPE endpoint
Enter fullscreen mode Exit fullscreen mode

This SQL query uses argMax to find the latest status and location based on the event_timestamp. The {{String(package_id, '')}} is a parameter that allows you to query the API with a specific package id. To get the full history of a package's updates, the package_history pipe looks like this:

DESCRIPTION >
    Get the full history of a package's status updates

NODE package_history_node
SQL >
    SELECT 
        package_id,
        event_timestamp,
        status,
        location,
        carrier,
        notes,
        updated_by
    FROM package_events
    WHERE package_id = {{String(package_id, '')}}
    ORDER BY event_timestamp DESC

TYPE endpoint
Enter fullscreen mode Exit fullscreen mode

The SQL query simply orders the events by event_timestamp in descending order, providing a complete timeline of the package's journey.

Carrier performance summary

The carrier_status_summary endpoint aggregates delivery statuses by carrier:

DESCRIPTION >
    Get a summary of package delivery statuses by carrier

NODE carrier_status_summary_node
SQL >
    SELECT 
        carrier,
        status,
        count() as package_count
    FROM (
        SELECT 
            package_id,
            carrier,
            argMax(status, event_timestamp) as status
        FROM package_events
        {% if defined(carrier) %}
        WHERE carrier = {{String(carrier, 'all')}}
        {% end %}
        GROUP BY package_id, carrier
    )
    GROUP BY carrier, status
    ORDER BY carrier, package_count DESC

TYPE endpoint
Enter fullscreen mode Exit fullscreen mode

This pipe demonstrates the use of conditional templating ({% if defined(carrier) %}) to optionally filter results by a specific carrier. To call these APIs, you can use the curl command with the appropriate parameters:

curl -X GET "https://api.europe-west2.gcp.tinybird.co/v0/pipes/package_status.json?token=%24TB_ADMIN_TOKEN&package_id=PKG12345678&utm_source=DEV&utm_campaign=tb+create+--prompt+DEV"
Enter fullscreen mode Exit fullscreen mode

Deploying to production

Deploying your Tinybird project to production is straightforward with the tb --cloud deploy command. This command creates scalable, production-ready API endpoints in the Tinybird Cloud. Here's an example:

tb --cloud deploy
Enter fullscreen mode Exit fullscreen mode

Tinybird manages resources as code, making it easy to integrate with CI/CD pipelines for automated deployments. Furthermore, token-based authentication secures your APIs. Here's how you might call a deployed endpoint:

curl -X GET "https://api.tinybird.co/v0/pipes/package_status.json?token=YOUR_PRODUCTION_TOKEN&package_id=PKG12345678&utm_source=DEV&utm_campaign=tb+create+--prompt+DEV"
Enter fullscreen mode Exit fullscreen mode

Conclusion

This tutorial walked you through setting up a real-time package tracking API using Tinybird. We covered how to ingest event data, transform it through pipes, and publish APIs for tracking package status, history, and carrier performance. Tinybird enables developers to build and deploy real-time data APIs at scale, without worrying about infrastructure. Whether you're handling package tracking or any other real-time data use case, Tinybird provides the tools you need to deliver fast, reliable insights. Sign up for Tinybird to build and deploy your first real-time data APIs in a few minutes.

Top comments (0)