Build Your First Fluid App
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:
- Completed the Installation guide
- Node.js 18+ installed
- A code editor (VS Code recommended)
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
- Open your browser to
http://localhost:8080 - Copy the URL (which now includes a container ID)
- Open the same URL in another browser tab or window
- Click the increment/decrement buttons in either window
- 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:
- Explore More Examples - See more complex applications
- Learn About Data Structures - Dive deeper into DDSes
- Add React Integration - Build with React
- 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