Build Your First Fluid App

getting startedtutorialcountercollaboration

Build Your First Fluid App

In this tutorial, you'll create a simple collaborative counter app that demonstrates the core concepts of Fluid Framework. Multiple users will be able to increment and decrement a shared counter in real-time.

What You'll Build

A collaborative counter where:

  • Multiple users can increment/decrement the same counter
  • Changes sync instantly across all connected clients
  • The state persists when users refresh or reconnect

Prerequisites

Make sure you have:

Step 1: Project Setup

Create a new project directory and initialize it:

mkdir fluid-counter-app
cd fluid-counter-app
npm init -y

Install the required dependencies:

# Core Fluid packages
npm install @fluidframework/tinylicious-client @fluidframework/map

# For this example, we'll use vanilla HTML/JS
# You can also use React, Vue, or any other framework

Step 2: Create the HTML Structure

Create an index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Collaborative Counter - Fluid Framework</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            text-align: center;
        }
        .counter {
            font-size: 3rem;
            margin: 30px 0;
            padding: 20px;
            border: 2px solid #007ACC;
            border-radius: 8px;
            background-color: #f0f8ff;
        }
        button {
            font-size: 1.5rem;
            padding: 10px 20px;
            margin: 0 10px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        .increment { background-color: #28a745; color: white; }
        .decrement { background-color: #dc3545; color: white; }
        .status { margin-top: 20px; font-style: italic; color: #666; }
    </style>
</head>
<body>
    <h1>Collaborative Counter</h1>
    <div class="counter" id="counter">0</div>
    <button class="decrement" id="decrementBtn">-</button>
    <button class="increment" id="incrementBtn">+</button>
    <div class="status" id="status">Connecting...</div>

    <script type="module" src="app.js"></script>
</body>
</html>

Step 3: Create the Fluid Application

Create an app.js file with the Fluid logic:

import { TinyliciousClient } from "@fluidframework/tinylicious-client";
import { SharedMap } from "@fluidframework/map";

// Define the container schema
const containerSchema = {
    initialObjects: {
        counterMap: SharedMap,
    },
};

class CollaborativeCounter {
    constructor() {
        this.client = new TinyliciousClient();
        this.container = null;
        this.counterMap = null;
        this.init();
    }

    async init() {
        // Get container ID from URL or create new container
        const containerId = this.getContainerIdFromUrl();

        if (containerId) {
            // Load existing container
            const { container } = await this.client.getContainer(containerId, containerSchema);
            this.container = container;
        } else {
            // Create new container
            const { container } = await this.client.createContainer(containerSchema);
            this.container = container;

            // Update URL with container ID for sharing
            const url = new URL(window.location);
            url.searchParams.set('containerId', container.id);
            window.history.replaceState({}, '', url);
        }

        // Get the SharedMap
        this.counterMap = this.container.initialObjects.counterMap;

        // Initialize counter value if it doesn't exist
        if (!this.counterMap.has('count')) {
            this.counterMap.set('count', 0);
        }

        // Set up event listeners
        this.setupEventListeners();

        // Initial render
        this.updateDisplay();
        this.updateStatus('Connected');
    }

    getContainerIdFromUrl() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('containerId');
    }

    setupEventListeners() {
        // Listen for changes to the counter
        this.counterMap.on('valueChanged', (changed) => {
            if (changed.key === 'count') {
                this.updateDisplay();
            }
        });

        // Button event listeners
        document.getElementById('incrementBtn').addEventListener('click', () => {
            this.increment();
        });

        document.getElementById('decrementBtn').addEventListener('click', () => {
            this.decrement();
        });

        // Container connection status
        this.container.on('connected', () => {
            this.updateStatus('Connected');
        });

        this.container.on('disconnected', () => {
            this.updateStatus('Disconnected');
        });
    }

    increment() {
        const currentValue = this.counterMap.get('count') || 0;
        this.counterMap.set('count', currentValue + 1);
    }

    decrement() {
        const currentValue = this.counterMap.get('count') || 0;
        this.counterMap.set('count', currentValue - 1);
    }

    updateDisplay() {
        const count = this.counterMap.get('count') || 0;
        document.getElementById('counter').textContent = count;
    }

    updateStatus(status) {
        document.getElementById('status').textContent = status;
    }
}

// Start the application
new CollaborativeCounter();

Step 4: Start the Local Service

In a separate terminal, start Tinylicious:

npx tinylicious

This starts the local Fluid service on http://localhost:7070.

Step 5: Serve Your Application

You can use any static file server. Here are a few options:

Option A: Using Python (if installed)

python -m http.server 8080

Option B: Using Node.js http-server

npx http-server -p 8080

Option C: Using Live Server (VS Code extension)

Open the project in VS Code and use the Live Server extension.

Step 6: Test Collaboration

  1. Open your browser to http://localhost:8080
  2. Copy the URL (which now includes a container ID)
  3. Open the same URL in another browser tab or window
  4. Click the increment/decrement buttons in either window
  5. Watch the counter update in real-time across both windows!

Understanding the Code

Let's break down the key concepts:

Container Schema

const containerSchema = {
    initialObjects: {
        counterMap: SharedMap,
    },
};

Defines what data structures your container will hold.

SharedMap

this.counterMap = this.container.initialObjects.counterMap;
this.counterMap.set('count', 0);  // Set a value
const count = this.counterMap.get('count');  // Get a value

A distributed key-value store that syncs across all clients.

Event Handling

this.counterMap.on('valueChanged', (changed) => {
    // React to changes from other clients
});

Listen for changes made by other collaborators.

What You've Learned

Key Concepts

You've successfully implemented real-time collaboration using Fluid Framework's core concepts.

  • Containers: Encapsulate collaborative data and behavior
  • Distributed Data Structures: Share state across multiple clients
  • Event-driven Architecture: React to changes from other users
  • Service Abstraction: Use different services (local vs production)

Enhancing Your App

Try these improvements:

Add User Identification

// Add user info to show who made changes
this.counterMap.set('lastUser', 'User123');

Add Reset Functionality

reset() {
    this.counterMap.set('count', 0);
}

Add Change History

// Track the history of changes
const history = this.counterMap.get('history') || [];
history.push({ user: 'User123', action: 'increment', timestamp: Date.now() });
this.counterMap.set('history', history);

Next Steps

Now that you've built your first Fluid app:

  1. Explore More Examples - See more complex applications
  2. Learn About Data Structures - Dive deeper into DDSes
  3. Add React Integration - Build with React
  4. Deploy to Production - Use Azure Fluid Relay

Troubleshooting

Container Not Loading

  • Ensure Tinylicious is running on port 7070
  • Check browser console for error messages
  • Verify all packages are installed correctly

Changes Not Syncing

  • Make sure both windows use the same container ID
  • Check network connectivity
  • Restart Tinylicious if needed

CORS Errors

Start Tinylicious with CORS enabled:

npx tinylicious --cors

Congratulations! You've built your first collaborative application with Fluid Framework. The same principles apply whether you're building a simple counter or a complex collaborative editor.

Last updated: September 17, 2024