How Data Flows from Client to Database in the Macro Platform

Data flows from client to database in Macro through an Axum HTTP router that authenticates requests via middleware, delegates to domain services, and executes compile-time checked SQLx queries against a PostgreSQL pool before returning serialized JSON responses.

The Macro platform (macro-inc/macro) implements a clean, service-oriented architecture that separates HTTP concerns from business logic. Understanding the data flow from client to database reveals a consistent pattern across services: Axum handles routing, middleware manages authentication, and SQLx provides type-safe database access through a PgPool. This architecture ensures that every request follows a predictable path from the initial HTTP POST to the final PostgreSQL INSERT or SELECT.

HTTP Routing with Axum

Client requests enter the system through Axum routers defined in each service. The router function in services/notification_service/src/api/mod.rs demonstrates how endpoints are registered and nested:

pub fn router<S: ::notification::domain::service::NotificationReader>() -> Router<ApiContext> {
    Router::new()
        .route(
            "/user_notifications",
            post(create_user_notification::<S>),
        )
        .layer(Extension::new(AuthorizationService::new()))
}

This pattern appears across services, including the unfurl service at services/unfurl_service/src/api/mod.rs, where Router::new().nest(...).merge(...) combines multiple route handlers into a unified API surface.

Authentication and Context Injection

Before reaching business logic, requests pass through the AuthorizationService middleware. This layer validates JWT or session tokens and injects a Context (or State) containing the user ID and a database pool connection. The middleware attaches this context to the request extensions, making it available to downstream handlers without coupling authentication details to domain logic.

Domain Service Layer

Handlers do not interact with the database directly. Instead, they delegate to domain services that encapsulate core business logic. In services/notification_service/src/api/user_notification.rs, the handler calls notification::domain::service::NotificationReader:

#[tracing::instrument(skip(state, payload))]
async fn create_user_notification<S>(
    State(state): State<ApiContext>,
    Json(payload): Json<CreateNotificationPayload>,
) -> Result<Json<Notification>, ApiError>
where
    S: NotificationReader,
{
    let notif = state
        .notification_service
        .create_notification(state.user_id, payload)
        .await?;
    Ok(Json(notif))
}

This separation ensures that HTTP transport concerns (JSON serialization, status codes) remain distinct from business rules and validation.

Database Access with SQLx

The domain service uses SQLx to execute compile-time checked queries against PostgreSQL. The PgPool connection pool is injected via the State or Context struct, allowing efficient connection reuse across concurrent requests.

The crates/webhook/src/outbound/pg_repository.rs file demonstrates the typical query pattern:

let inserted = sqlx::query!(
    r#"
    INSERT INTO notifications (user_id, title, body)
    VALUES ($1, $2, $3)
    RETURNING id
    "#,
    user_id,
    payload.title,
    payload.body,
)
.fetch_one(pool)
.await?;

SQLx validates these queries at build time against the database schema defined in crates/macro_db_client/migrations/, ensuring type safety before deployment.

Result Mapping and Response Serialization

Database rows are mapped to domain structs through conversion functions. The row_to_webhook function in crates/webhook/src/outbound/pg_repository.rs illustrates this pattern:

fn row_to_webhook(row: WebhookRow) -> Result<Webhook, sqlx::Error> {
    Ok(Webhook {
        id: row.id,
        url: row.url,
        status: WebhookStatus::from_str(&row.status)?,
        // … other fields …
    })
}

The domain struct travels back up the call stack, where Axum serializes it to JSON and sends the response to the client.

Complete Data Flow Example: Creating a Notification

The following trace illustrates the complete data flow when a client creates a notification:

  1. Client Request: Sends POST /user_notifications with JSON payload
  2. Route Matching: Axum router (services/notification_service/src/api/mod.rs) matches the path
  3. Authentication: AuthorizationService validates the token and injects user context
  4. Handler Execution: create_user_notification in services/notification_service/src/api/user_notification.rs receives the request
  5. Business Logic: NotificationReader service validates the payload and prepares the database call
  6. Database Execution: SQLx runs the prepared INSERT statement against the MacroDB PostgreSQL instance
  7. Result Mapping: The returned row ID converts to a Notification struct
  8. Response: Axum serializes the struct as JSON and returns it to the client

Key Files in the Data Flow Pattern

Summary

  • Macro uses Axum for HTTP routing, with middleware-based authentication injecting user context and database pools into request handlers.
  • Domain services separate business logic from HTTP concerns, with handlers delegating to traits like NotificationReader rather than accessing databases directly.
  • SQLx with PostgreSQL provides compile-time checked queries through the query! macro, using PgPool for efficient connection management.
  • Row mapping functions convert raw SQLx results into strongly-typed domain structs before JSON serialization.
  • Consistent patterns across notification_service and unfurl_service demonstrate a reusable architecture for client-to-database data flows.

Frequently Asked Questions

What web framework does Macro use for routing client requests?

Macro uses Axum, a Rust web framework built on Tokio. The routing logic is centralized in each service's src/api/mod.rs file, where Router::new() instances are configured with routes, handlers, and middleware layers like AuthorizationService.

How does Macro handle authentication in the data flow?

Authentication occurs in middleware before reaching business logic. The AuthorizationService validates JWT or session tokens and injects a Context struct containing the user ID and database pool into the request extensions. This context is then accessible to handlers via Axum's State extractor.

What database does Macro use and how is it accessed?

Macro uses PostgreSQL accessed through SQLx. The PgPool connection pool is created at service startup and injected into the application state. Domain services execute queries using SQLx's query! macro, which validates SQL syntax and types against the database schema at compile time.

How does SQLx ensure type safety in Macro's database queries?

SQLx connects to the database at build time using the schema defined in crates/macro_db_client/migrations/ to validate queries in the query! macro. If a query references a non-existent column or has a type mismatch, the Rust compiler returns an error before runtime, ensuring that only valid SQL reaches the production database.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →