In October 2025, I joined Truxo.ai as Senior Software Engineer and Mobile Lead. Truxo is building an AI-powered Transportation Management System (TMS) that serves real trucking operations across the United States — connecting drivers, carriers, brokers, and dispatchers into a single platform that handles everything from load matching to invoicing to compliance.
I am Manjodh Singh Saran, a full stack developer based in Ludhiana, India. This is the story of how we built Truxo — the technical decisions, the architecture, the challenges we faced, and the lessons I learned building software for an industry that moves $940 billion worth of goods annually in the US alone.
This is not a marketing piece. It is a technical case study for developers who want to understand what it looks like to build a complex, production-grade system for a real industry with real constraints.
The Problem: Trucking Is Broken (Technically)
The US trucking industry is massive but technologically fragmented. A typical trucking operation in 2024 uses:
- A separate app for load matching (finding freight to haul)
- A different platform for dispatching
- Paper-based or PDF bills of lading
- Spreadsheets for invoicing
- Phone calls and text messages for driver communication
- Manual compliance tracking for Hours of Service (HOS) regulations
The result is operational chaos. Dispatchers spend hours on the phone coordinating loads. Drivers deal with paperwork at every pickup and delivery. Brokers lose visibility into shipment status. Payments take 30-90 days because invoicing is manual.
Truxo's thesis is simple: unify all of this into one AI-powered platform. One system where loads are matched intelligently, dispatch happens in real-time, documents are processed automatically, payments are fast, and compliance is built into the workflow.
When I joined, the platform had early traction with real carriers. My job was to lead the mobile experience for drivers (the Truxo Tracker app) while contributing to backend systems across the platform.
Architecture Overview
Truxo is a multi-service system. Here is the high-level architecture:
┌─────────────────────────────────────────────────────────┐
│ Client Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Truxo Web │ │ Truxo Tracker│ │ Broker Portal│ │
│ │ (React/Next) │ │ (React Native│ │ (React/Next) │ │
│ │ │ │ + Expo) │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ API Gateway / Load Balancer │
└────────────────────────┬────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Auth │ │ Shipment │ │ AI/ML │
│ Service │ │ Service │ │ Service │
│ (FastAPI) │ │ (FastAPI) │ │ (FastAPI) │
└────────────┘ └────────────┘ └────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ PostgreSQL │ Redis │ S3 │
└─────────────────────────────────────────┘
The system is split into multiple FastAPI services, each responsible for a bounded domain. The client layer includes a web dashboard (Next.js), the Truxo Tracker mobile app (React Native + Expo), and a broker-facing portal.
Why FastAPI Over Express
We chose Python with FastAPI for the backend instead of Node.js/Express. I have written a detailed FastAPI vs Express comparison covering when to use each — for Truxo, the decision came down to three factors:
1. AI/ML ecosystem. Truxo's core differentiator is AI — document parsing, load matching optimization, route planning, anomaly detection. Python's ML ecosystem (scikit-learn, transformers, langchain) is unmatched. Running AI models in a Python backend eliminates the overhead of calling out to a separate ML service.
2. Type safety with Pydantic. FastAPI's integration with Pydantic gives us runtime type validation on every request and response. This is better than Express with Zod because Pydantic also generates OpenAPI documentation automatically. Every endpoint is documented the moment it is written.
3. Async performance. FastAPI is built on Starlette and supports async/await natively. For I/O-bound operations (database queries, external API calls, S3 uploads), the performance is excellent. We benchmarked it against Express for our use case and the throughput was comparable, with the added benefit of the Python ecosystem.
The tradeoff is that we lose TypeScript code sharing between frontend and backend. We mitigate this by generating TypeScript types from our OpenAPI spec using openapi-typescript-codegen. The types are auto-generated on every backend deploy and published as an internal package that the frontend and mobile apps consume.
The Truxo Tracker Mobile App
Technical Stack
The Truxo Tracker app is built with:
- React Native + Expo (managed workflow with EAS Build)
- Expo Router for file-based navigation
- Redux Toolkit + RTK Query for state management and API calls
- expo-location for real-time GPS tracking
- expo-notifications for push notifications
- expo-camera for document scanning
- expo-secure-store for token storage
I have written extensively about my approach to building production React Native apps in my React Native Expo production guide. The Truxo Tracker app applies every principle from that guide at scale.
Real-Time Shipment Tracking
The most critical feature of the driver app is real-time location tracking. Dispatchers and brokers need to know where every truck is at all times. Drivers need to see their route, next stops, and estimated arrival times.
The challenge: GPS tracking is a battery killer. Running continuous high-accuracy GPS on a phone for a 10-hour driving shift will drain the battery by noon.
Our solution uses a tiered tracking approach:
// Simplified tracking logic
import * as Location from "expo-location";
import * as TaskManager from "expo-task-manager";
const TRACKING_TASK = "shipment-tracking";
TaskManager.defineTask(TRACKING_TASK, async ({ data, error }) => {
if (error) {
console.error("Tracking error:", error);
return;
}
const { locations } = data as { locations: Location.LocationObject[] };
const latest = locations[locations.length - 1];
// Batch location updates and send every 30 seconds
await LocationBuffer.add(latest);
if (LocationBuffer.shouldFlush()) {
await api.sendLocationBatch(LocationBuffer.flush());
}
});
export async function startTracking(shipmentId: string) {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") throw new Error("Location permission denied");
// Background tracking with battery-optimized settings
await Location.startLocationUpdatesAsync(TRACKING_TASK, {
accuracy: Location.Accuracy.Balanced, // Not Highest — saves battery
timeInterval: 10000, // Update every 10 seconds
distanceInterval: 50, // Or every 50 meters
foregroundService: {
notificationTitle: "Truxo Tracker",
notificationBody: `Tracking shipment ${shipmentId}`,
},
pausesUpdatesAutomatically: true, // Pause when stationary
});
}Key decisions:
- Balanced accuracy instead of highest accuracy. The difference is negligible for trucking (we need to know which highway, not which lane) but the battery savings are significant.
- Batched uploads instead of per-point uploads. We buffer location points and send them in batches every 30 seconds. This reduces network requests by 10x.
- Pause when stationary. When the truck is parked (at a loading dock, rest stop), we stop GPS updates entirely and resume when motion is detected.
- Foreground service notification. Required by Android for background location access. We use it as a feature — showing the driver their current tracking status.
This approach gives us all-day tracking with less than 5% additional battery drain over normal phone usage.
Document Scanning and AI Processing
Trucking involves a lot of paperwork: Bills of Lading (BOL), Proof of Delivery (POD), rate confirmations, insurance certificates. Drivers traditionally photograph these and email them. With Truxo, they scan documents directly in the app.
// Document capture flow
async function captureDocument(type: DocumentType) {
const result = await ImagePicker.launchCameraAsync({
quality: 0.8, // Balance between quality and upload size
allowsEditing: true,
aspect: [4, 3],
});
if (result.canceled) return;
// Upload to backend
const formData = new FormData();
formData.append("file", {
uri: result.assets[0].uri,
type: "image/jpeg",
name: `${type}_${Date.now()}.jpg`,
} as any);
formData.append("document_type", type);
const response = await api.uploadDocument(formData);
// Backend processes with AI:
// 1. OCR extracts text
// 2. AI classifies document type
// 3. Relevant fields are auto-extracted (shipper, consignee, weight, etc.)
// 4. Data is linked to the correct shipment
return response;
}On the backend, uploaded documents go through an AI pipeline:
- OCR using Tesseract and cloud-based OCR services for handwritten text
- Document classification using a fine-tuned model that identifies BOLs, PODs, invoices, etc.
- Field extraction that pulls structured data from the unstructured document — shipper name, consignee, weight, piece count, reference numbers
- Validation against the shipment record to flag discrepancies
This pipeline replaces hours of manual data entry. When a driver scans a BOL at pickup, the shipment record is automatically populated with the correct details within seconds.
Authentication and Multi-Role Access
Truxo serves multiple user types: drivers, dispatchers, carrier admins, and brokers. Each has different permissions and data access patterns.
Our auth architecture:
- JWT access tokens with 15-minute expiry for API authentication
- Refresh tokens stored in HTTP-only cookies (web) and expo-secure-store (mobile)
- Role-based access control at the API layer
- Organization-scoped data — a carrier admin sees only their company's shipments
The mobile app handles token refresh transparently:
// RTK Query base query with automatic token refresh
const baseQueryWithReauth = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions);
if (result.error?.status === 401) {
// Try to refresh the token
const refreshResult = await baseQuery(
{ url: "/auth/refresh", method: "POST" },
api,
extraOptions
);
if (refreshResult.data) {
// Store new token
const { accessToken } = refreshResult.data as AuthResponse;
await SecureStore.setItemAsync("access_token", accessToken);
api.dispatch(setToken(accessToken));
// Retry the original request
result = await baseQuery(args, api, extraOptions);
} else {
// Refresh failed — log out
api.dispatch(logout());
}
}
return result;
};This pattern ensures the driver never sees an auth error during their shift. The token refreshes silently in the background.
Real-Time Communication
Dispatchers need to communicate with drivers in real-time. We built an in-app messaging system rather than relying on phone calls or SMS.
The architecture uses a polling-based approach with push notification triggers:
- The mobile app polls for new messages every 30 seconds when the chat screen is open
- When the dispatcher sends a message, the backend sends a push notification to the driver
- The notification triggers an immediate message fetch, regardless of polling interval
- Messages are stored in PostgreSQL with read receipts
We considered WebSockets but decided against them for the mobile use case. Maintaining a persistent WebSocket connection on a mobile device moving through areas with spotty cellular coverage is unreliable. The polling + push notification approach is more resilient and still delivers messages within seconds.
Challenges and Lessons Learned
Challenge 1: Offline-First Architecture
Truck drivers pass through areas with no cellular coverage — rural highways, mountain passes, underground loading docks. The app needs to work when there is no network.
Our solution:
- Queue offline actions. When the driver updates a shipment status, takes a photo, or submits a form offline, the action is queued in local storage.
- Sync when reconnected. A background sync process monitors network connectivity and replays queued actions when the connection returns.
- Conflict resolution. If the same shipment was updated by the driver (offline) and the dispatcher (online), we use a last-write-wins strategy with timestamps, flagging conflicts for manual review.
// Offline action queue
class OfflineQueue {
private queue: QueuedAction[] = [];
async add(action: QueuedAction) {
this.queue.push({
...action,
timestamp: Date.now(),
retryCount: 0,
});
await this.persist();
}
async sync() {
const pending = [...this.queue];
for (const action of pending) {
try {
await this.execute(action);
this.queue = this.queue.filter((a) => a.id !== action.id);
} catch (error) {
if (action.retryCount >= 3) {
// Move to dead letter queue for manual intervention
await this.moveToDLQ(action);
this.queue = this.queue.filter((a) => a.id !== action.id);
} else {
action.retryCount++;
}
}
}
await this.persist();
}
}Challenge 2: Background Location on iOS
Apple is strict about background location access. Getting background location working reliably on iOS requires:
- Adding the
locationbackground mode inapp.json - Providing a clear, specific purpose string explaining why you need background location
- Using
startLocationUpdatesAsyncwith a foreground service notification - Submitting a detailed App Store review explanation with a demo video
Our first submission was rejected because our purpose string was too vague ("to track shipments"). We rewrote it to specifically explain that drivers need continuous tracking during active deliveries so dispatchers can provide ETAs to customers. The second submission was approved.
Challenge 3: Handling Diverse Android Devices
Truxo's drivers use a wide range of Android devices — from flagship Samsungs to budget phones that cost under $100. Performance varies dramatically across this range.
Our approach:
- Performance budgets. Every screen must render in under 300ms on a mid-range device. We test on Samsung Galaxy A23 as our baseline.
- Image optimization. We compress document photos to 80% JPEG quality before upload and use progressive loading for all images.
- Memory management. We aggressively unmount components when navigating away and use FlashList instead of FlatList for all scrollable lists — techniques I cover in depth in my React Native performance optimization guide.
- Graceful degradation. If a device cannot handle smooth animations, we disable them. A functional app without animations is better than a beautiful app that crashes.
Challenge 4: Compliance and Data Privacy
Trucking is a regulated industry. We deal with Hours of Service (HOS) regulations, FMCSA compliance, and sensitive business data (rates, contracts, financial information).
Our compliance approach:
- Data encryption in transit (TLS 1.3) and at rest (AWS RDS encryption)
- Audit logging on every data mutation. Who changed what, when, and from which device.
- Data retention policies aligned with DOT record-keeping requirements (6 months minimum for most records)
- SOC 2 Type II compliance as a target for our security practices
Results and Impact
Since launching Truxo Tracker:
- Document processing time dropped from 20+ minutes of manual data entry to under 30 seconds with AI-assisted scanning
- Shipment visibility went from phone calls and text messages to real-time GPS tracking with ETAs
- Driver app adoption reached 90%+ among active carriers within the first two months
- Invoicing cycle shortened from 30-45 days to under 7 days through automated document matching
The most rewarding metric is driver feedback. Truck drivers are not known for being early adopters of technology. When a driver tells you the app actually makes their job easier, you know you got something right.
Technical Takeaways for Developers
If you are building a complex, real-world system, here is what I would emphasize:
1. Domain knowledge matters as much as technical skill. I spent my first month at Truxo learning the trucking industry — talking to drivers, dispatchers, and carriers. Understanding the domain shaped every technical decision.
2. Mobile apps for field workers have different constraints than consumer apps. Battery life, offline capability, diverse device support, and rugged use conditions are first-class concerns.
3. AI features need graceful fallbacks. Our document AI works well, but it is not 100% accurate. Every AI-processed field has a manual override. Users correct the AI, and those corrections feed back into model improvement.
4. Start with a monolith, extract services when you have a reason. Truxo started as a single FastAPI application. We extracted services as specific domains (auth, shipments, AI processing) grew complex enough to justify independent deployment.
5. Cross-functional collaboration is not a soft skill — it is a technical skill. I work directly with product, design, and operations teams. The best technical architecture is one that aligns with how the business actually operates.
Building Truxo has been the most technically challenging and professionally rewarding work of my career. If you are interested in the broader architecture patterns I use across projects, check out my full stack developer roadmap or visit my blog for more technical deep dives.
You can download Truxo Tracker on the App Store and Google Play, or learn more about the platform at truxo.ai.
For developers building similar real-time, data-intensive mobile applications, my React Native Expo production guide covers the foundational patterns that made Truxo Tracker possible.