# How the Meetily Frontend Invokes Tauri Commands and Listens for Rust Events

> Explore how Meetily's frontend invokes Tauri commands and listens for Rust events. Discover real-time data flow between Next.js UI and Rust backend.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: how-to-guide
- Published: 2026-07-12

---

**Meetily's Next.js frontend communicates with its Rust backend using Tauri's `invoke` API to execute commands and the `listen` API to subscribe to events, enabling real-time bidirectional data flow between the React UI and native audio processing code.**

The Meetily desktop application bridges a Next.js React interface with a high-performance Rust core using Tauri's inter-process communication layer. Understanding how the frontend invokes Tauri commands and listens for events from Rust reveals the architecture that keeps the UI responsive while handling compute-intensive tasks like audio capture, VAD processing, and Whisper transcription.

## Invoking Tauri Commands from TypeScript

The frontend centralizes all Rust command calls inside service modules. In [`frontend/src/services/recordingService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/services/recordingService.ts), the application imports ** `invoke` ** from `@tauri-apps/api/core` and exposes thin async wrappers that forward arguments to the Rust side.

Every command registered in the Tauri handler can be triggered using the string identifier defined in Rust. The recording service implements standard CRUD-style operations for the recorder:

```typescript
// frontend/src/services/recordingService.ts
import { invoke } from '@tauri-apps/api/core';

export async function startRecording() {
  return invoke('start_recording');
}

export async function stopRecording() {
  return invoke('stop_recording');
}

export async function pauseRecording() {
  return invoke('pause_recording');
}

export async function resumeRecording() {
  return invoke('resume_recording');
}

```

Additional commands manage AI model lifecycles and analytics. The frontend invokes `whisper_load_model` to initialize transcription engines, `builtin_ai_download_model` for on-device LLMs, and `track_event` for telemetry, all following the same pattern of passing a command name and optional payload to `invoke`.

## Listening for Rust Events in React

While commands follow a request-response pattern, events enable the Rust backend to push updates to the frontend asynchronously. The application imports ** `listen` ** from `@tauri-apps/api/event` to subscribe to channels that the Rust code emits via `app.emit()`.

### Recording Lifecycle Events

The recording service exposes functions that return ** `UnlistenFn` ** callbacks, allowing React components to subscribe to state changes:

```typescript
// frontend/src/services/recordingService.ts
import { listen } from '@tauri-apps/api/event';

export async function onRecordingStarted(callback: () => void) {
  return listen('recording-started', callback);
}

export async function onRecordingStopped(callback: () => void) {
  return listen('recording-stopped', callback);
}

export async function onRecordingPaused(callback: () => void) {
  return listen('recording-paused', callback);
}

export async function onRecordingResumed(callback: () => void) {
  return listen('recording-resumed', callback);
}

```

### Transcription Streaming Events

Real-time transcription updates flow through [`frontend/src/services/transcriptService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/services/transcriptService.ts), which listens for chunk-level updates and completion signals:

```typescript
// frontend/src/services/transcriptService.ts
import { listen } from '@tauri-apps/api/event';

export interface TranscriptPayload {
  text: string;
  isFinal: boolean;
}

export async function onTranscriptUpdate(callback: (event: { payload: TranscriptPayload }) => void) {
  return listen('transcript-update', callback);
}

export async function onTranscriptionComplete(callback: (event: { payload: { text: string } }) => void) {
  return listen('transcription-complete', callback);
}

export async function onTranscriptionError(callback: (event: { payload: { error: string } }) => void) {
  return listen('transcription-error', callback);
}

```

## Managing Listener Lifecycles in React Hooks

To prevent memory leaks, hooks like `useRecordingStop` (found in [`frontend/src/hooks/useRecordingStop.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/hooks/useRecordingStop.ts)) store the `unlisten` functions returned by the service layer and invoke them during component unmount.

```typescript
// frontend/src/hooks/useRecordingStop.ts
import { useEffect, useRef } from 'react';
import { onRecordingStopped } from '@/services/recordingService';

export function useRecordingStop(onStop: () => void) {
  const unlistenRef = useRef<(() => void) | null>(null);

  useEffect(() => {
    const setupListener = async () => {
      const unlisten = await onRecordingStopped(() => {
        onStop();
      });
      unlistenRef.current = unlisten;
    };

    setupListener();

    return () => {
      if (unlistenRef.current) {
        unlistenRef.current();
      }
    };
  }, [onStop]);
}

```

This pattern ensures that Rust events do not attempt to update unmounted React components, which is critical in a desktop environment where users frequently switch views during long-running recordings.

## Rust Implementation: Command Registration and Event Emission

On the Rust side, commands are defined in [`frontend/src-tauri/src/lib.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/lib.rs) using the `#[tauri::command]` attribute macro and registered via `tauri::generate_handler!`.

```rust
// frontend/src-tauri/src/lib.rs
#[tauri::command]
async fn start_recording(
    state: State<'_, RecordingState>,
    device_names: Vec<String>,
    meeting_name: String,
) -> Result<String, String> {
    // Audio pipeline initialization
    Ok("recording_started".to_string())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            start_recording,
            stop_recording,
            pause_recording,
            resume_recording,
            whisper_load_model,
            track_event,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

During execution, Rust emits events through the Tauri application handle. For example, when recording actually begins, the backend emits the `recording-started` event that the frontend is already awaiting:

```rust
// frontend/src-tauri/src/audio/recordingManager.rs (conceptual)
app.emit("recording-started", Payload { timestamp: now });

```

## Complete Data Flow Example

The full communication cycle illustrates the separation of concerns:

1. **User Action**: React component calls `startRecording()` service function
2. **Invocation**: Service executes `invoke('start_recording', { device_names, meeting_name })`
3. **Rust Execution**: Command handler in [`lib.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/lib.rs) receives arguments and initializes the audio pipeline
4. **Event Emission**: When hardware initialization succeeds, Rust calls `app.emit("recording-started", ...)`
5. **Frontend Update**: The `onRecordingStarted` listener in the React hook receives the payload and updates component state
6. **Cleanup**: Component unmount triggers the `unlisten` function, removing the subscription

This architecture ensures that computationally expensive work stays in Rust while the React UI remains responsive to user input.

## Summary

- **Command Invocation**: The frontend uses `invoke` from `@tauri-apps/api/core` in [`frontend/src/services/recordingService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/services/recordingService.ts) to trigger Rust functions like `start_recording` and `whisper_load_model`.
- **Event Subscription**: The `listen` function from `@tauri-apps/api/event` creates subscriptions to Rust-emitted events including `recording-started`, `transcript-update`, and `transcription-complete`.
- **Memory Management**: React hooks store `UnlistenFn` callbacks returned by listeners and clean them up on unmount to prevent leaks.
- **Rust Registration**: Commands are defined with `#[tauri::command]` in [`frontend/src-tauri/src/lib.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/lib.rs) and emitted via `app.emit()` from managers like [`recordingManager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/recordingManager.rs).

## Frequently Asked Questions

### How does Meetily pass arguments when invoking Tauri commands?

Meetily passes arguments as the second parameter to `invoke`. For example, when starting a recording, the frontend calls `invoke('start_recording', { device_names, meeting_name })`, where the object matches the parameter signature defined in the Rust `#[tauri::command]` handler. The Tauri framework automatically serializes the JavaScript object into Rust types.

### What events does the Meetily Rust backend emit to the frontend?

The backend emits recording lifecycle events (`recording-started`, `recording-stopped`, `recording-paused`, `recording-resumed`) through [`recordingService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/recordingService.ts), and transcription-specific events (`transcript-update`, `transcription-complete`, `transcription-error`) through [`transcriptService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/transcriptService.ts). These events carry typed payloads that TypeScript interfaces define for type safety.

### How does Meetily prevent memory leaks from event listeners?

Each call to `listen` returns an `UnlistenFn` that removes the subscription. React hooks like `useRecordingStop` store this function in a ref and invoke it inside the effect cleanup function when the component unmounts, ensuring that Rust cannot emit events to destroyed React components.

### Where are Tauri commands registered in the Meetily codebase?

Commands are registered in [`frontend/src-tauri/src/lib.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/lib.rs) using the `tauri::generate_handler!` macro, which takes a list of functions marked with `#[tauri::command]`. This registration occurs during the Tauri application build phase within the `main()` function before `tauri::Builder::default().run()` executes.