Skip to main content
Architecture8 min readFebruary 1, 2026

Routiine App: Mobile-First Architecture With Expo and Hono

The architecture behind Routiine App — an on-demand mobile services marketplace built with Expo, Hono, Prisma, and PostGIS for location-based service delivery in DFW.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

A Different Kind of Routiine

Routiine App shares a brand name with Routiine.io but is a completely different product. Routiine.io is a sales intelligence CRM — a web application for sales teams. Routiine App is a mobile-first marketplace for on-demand services, starting with windshield chip repair in the Dallas-Fort Worth area.

The connection between the two is the parent brand and the long-term vision of Routiine as a platform company. But the technical challenges, the user personas, and the architectural patterns are distinct enough that they share almost no code. This article focuses on the mobile architecture specifically.

Why Expo Over Native

The framework decision for the mobile app came down to three options: native iOS and Android development, React Native with a bare workflow, or Expo managed workflow. Each has trade-offs, and the choice depends heavily on the team's capabilities and the app's requirements.

Native development produces the best possible user experience and access to platform APIs. But it requires maintaining two codebases in two languages (Swift and Kotlin), which effectively doubles the development effort for a small team. For a marketplace MVP where speed to market matters more than pixel-perfect platform-specific UI, native development was over-invested.

React Native bare workflow provides native performance with a single JavaScript codebase. But the bare workflow requires managing native dependencies, build configurations, and platform-specific code manually. The setup and maintenance overhead is significant, especially for native modules like camera access, push notifications, and payment processing.

Expo managed workflow abstracts the native complexity behind a consistent API surface. The trade-off is that you are limited to the capabilities Expo provides — you cannot add arbitrary native modules without ejecting. For our requirements — maps, camera, push notifications, payments, location services — Expo's built-in modules covered everything we needed. The development velocity advantage was substantial: a single expo start command launches the dev server, and builds are handled through Expo's cloud build service.

The decision was Expo managed workflow, with the understanding that we would eject to bare workflow only if we encountered a native capability requirement that Expo could not satisfy. So far, we have not needed to eject.

Backend: Hono on Bun

The backend uses Hono, a lightweight web framework designed for edge and serverless environments. Hono was chosen over Express or Fastify for its TypeScript-first design, its minimal footprint, and its compatibility with Bun as a runtime.

The marketplace backend handles four primary concerns: user management (customers and service providers), location-based service matching, job lifecycle management, and payment processing through Stripe Connect.

Hono's routing is straightforward and middleware-based. Authentication middleware validates JWTs on every request. Tenant context middleware identifies the market (DFW for the MVP). Rate limiting middleware prevents abuse. Each concern is a composable middleware layer rather than a monolithic handler, which keeps the route handlers focused on business logic.

The ORM is Prisma, connecting to PostgreSQL with the PostGIS extension for geographic queries. Prisma was chosen here (over Drizzle, which we used for Routiine.io) because the data model is more relationship-heavy than query-heavy. The marketplace entities — users, providers, jobs, reviews, payments — have deep relationships that Prisma's relation queries handle elegantly. The complex geographic queries use raw SQL through Prisma's $queryRaw method, which gives us direct access to PostGIS functions without losing type safety on the result.

The Service Request Flow

The core user flow in the Routiine App is: customer discovers damage, opens the app, submits a service request with photos and location, gets matched with a nearby provider, receives a quote, approves it, and gets the repair done — typically within hours, not days.

The architecture supporting this flow is designed around asynchronous messaging. When a customer submits a service request, the backend does not synchronously find a provider and return a match. Instead, it creates the request record, acknowledges receipt to the customer, and pushes the request into a matching queue.

The matching service processes the queue by querying for available providers within a configurable radius of the customer's location, sorted by proximity and rating. Eligible providers receive a push notification with the job details. The first provider to accept the job is assigned. If no provider accepts within a timeout window, the radius expands and the next tier of providers is notified.

This asynchronous model was chosen over synchronous matching for resilience. If the matching service is temporarily unavailable, customer requests are queued and processed when it recovers. The customer always gets an immediate acknowledgment, and the matching happens in the background. This is the same pattern used by ride-sharing apps, adapted for a service marketplace with different time sensitivity — chip repair can wait fifteen minutes for a match, unlike a ride request.

Offline Considerations

Mobile apps operate in environments with unreliable connectivity. A customer submitting a service request from a parking garage may have spotty signal. A provider updating job status while on a rural highway may lose connectivity entirely.

The architecture handles this through optimistic UI updates and retry queues. When a user performs an action — submitting a request, accepting a job, updating status — the UI updates immediately based on the expected outcome. The actual API call is queued and retried if it fails due to connectivity. When connectivity returns, the queued actions are submitted in order.

For critical operations like payment confirmation, the retry is supplemented by server-side idempotency. Each payment operation includes an idempotency key, so retried requests produce the same result without double-charging. This pattern is standard for payment integrations but essential for mobile-first applications where retries are common.

The offline architecture adds complexity to the client-side state management. The app maintains a local SQLite database for offline-capable data — the user's active jobs, recent history, and cached provider information. This local database synchronizes with the server when connectivity is available, with conflict resolution that favors the server state for shared data and the client state for in-progress operations.