# How Data Flows from Client to Database in the Macro Platform

> Discover how data flows client to database in Macro. Learn about Axum routing, middleware authentication, domain services, and SQLx queries to PostgreSQL.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: architecture
- Published: 2026-08-21

---

**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`](https://github.com/macro-inc/macro/blob/main/services/notification_service/src/api/mod.rs) demonstrates how endpoints are registered and nested:

```rust
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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/services/notification_service/src/api/user_notification.rs), the handler calls `notification::domain::service::NotificationReader`:

```rust
#[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`](https://github.com/macro-inc/macro/blob/main/crates/webhook/src/outbound/pg_repository.rs) file demonstrates the typical query pattern:

```rust
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`](https://github.com/macro-inc/macro/blob/main/crates/webhook/src/outbound/pg_repository.rs) illustrates this pattern:

```rust
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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/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

- **[`services/notification_service/src/api/mod.rs`](https://github.com/macro-inc/macro/blob/main/services/notification_service/src/api/mod.rs)**: Defines Axum routers and attaches authentication middleware using `Extension::new(AuthorizationService::new())`.
- **[`services/notification_service/src/api/user_notification.rs`](https://github.com/macro-inc/macro/blob/main/services/notification_service/src/api/user_notification.rs)**: Implements HTTP handlers that bridge Axum's HTTP layer and the domain service layer.
- **[`crates/webhook/src/outbound/pg_repository.rs`](https://github.com/macro-inc/macro/blob/main/crates/webhook/src/outbound/pg_repository.rs)**: Contains SQLx query implementations showing `sqlx::query!` usage and the `row_to_webhook` mapping pattern for converting PostgreSQL rows to domain models.
- **[`services/unfurl_service/src/api/mod.rs`](https://github.com/macro-inc/macro/blob/main/services/unfurl_service/src/api/mod.rs)**: Alternative example of router composition using `api_router` with nested and merged routes.
- **`crates/macro_db_client/migrations/`**: Houses PostgreSQL schema definitions that enable SQLx's compile-time query validation.

## 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`](https://github.com/macro-inc/macro/blob/main/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.