# 🌐 FX API Plugin - Complete Cheat Sheet
## 🎯 **Plugin Overview**
**FX API** provides a convenient abstraction over FX core's built-in `@/api/` support. It maintains FX's zero async contamination principle while offering a clean, familiar API interface.
### **Core Features**
- ✅ **All HTTP Verbs** - GET, POST, PUT, PATCH, DELETE support
- ✅ **Zero Async Contamination** - Uses FX's FutureProxy internally
- ✅ **Immediate Response** - Synchronous surface with background processing
- ✅ **Base URL Support** - Configure API base URLs
- ✅ **Request Interceptors** - Modify requests before sending
- ✅ **Response Interceptors** - Transform responses after receiving
- ✅ **Convenience Methods** - Cleaner syntax over raw FX @/api/
### **Key Principle**
This plugin is a **convenience wrapper** - it doesn't replace FX's built-in API support but makes it easier to use while preserving all FX benefits.
---
## 📦 **Installation & Setup**
### **Loading the Plugin**
```html
```
### **Basic Usage**
```javascript
// Simple requests - immediate FX nodes returned
const users = $api.get('/users');
const newUser = $api.post('/users', { name: 'John', email: 'john@example.com' });
// Access data reactively
users.watch(data => console.log('Users updated:', data));
newUser.watch(result => console.log('User created:', result));
```
---
## 🚀 **Core API Reference**
### **HTTP Methods**
| Method | Syntax | Description |
|--------|--------|-------------|
| `get(url, args?)` | `$api.get('/users')` | GET request |
| `post(url, body?, args?)` | `$api.post('/users', userData)` | POST request |
| `put(url, body?, args?)` | `$api.put('/users/42', userData)` | PUT request |
| `patch(url, body?, args?)` | `$api.patch('/users/42', updates)` | PATCH request |
| `delete(url, args?)` | `$api.delete('/users/42')` | DELETE request |
### **Endpoint Builder**
```javascript
// Create endpoint-specific interface
const usersAPI = $api.endpoint('/users');
// Use all HTTP methods on this endpoint
const allUsers = usersAPI.get();
const newUser = usersAPI.post({ name: 'John' });
const updatedUser = usersAPI.put({ id: 42, name: 'Updated' });
const patchedUser = usersAPI.patch({ name: 'Patched' });
usersAPI.delete();
```
### **Base URL Builder**
```javascript
// Create API instance with different base URL
const v1API = $api.base('https://api.example.com/v1');
const v2API = $api.base('https://api.example.com/v2');
// Use different versions
const v1Users = v1API.get('/users');
const v2Users = v2API.get('/users');
```
---
## 🔧 **Advanced Configuration**
### **Request Interceptors**
```javascript
// Add authentication headers automatically
const authenticatedAPI = $api.addRequestInterceptor((config) => {
const token = $$('auth.token').get();
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
});
// Add API versioning
const versionedAPI = $api.addRequestInterceptor((config) => {
config.headers = {
...config.headers,
'API-Version': '2024-01-01'
};
return config;
});
```
### **Response Interceptors**
```javascript
// Transform response data
const transformedAPI = $api.addResponseInterceptor((response, config) => {
// Add metadata to all responses
return {
...response,
_metadata: {
requestedAt: Date.now(),
endpoint: config.url,
method: config.method
}
};
});
// Error handling interceptor
const errorHandledAPI = $api.addResponseInterceptor((response, config) => {
if (response.error) {
// Log errors to FX node for global error handling
$$('api.errors').set({
error: response.error,
endpoint: config.url,
timestamp: Date.now()
});
}
return response;
});
```
### **Configuration Chaining**
```javascript
// Build complex API configurations
const productionAPI = $api
.base('https://prod-api.example.com')
.defaults({
headers: {
'Environment': 'production',
'Client-Version': '1.0.0'
}
})
.addRequestInterceptor(authInterceptor)
.addResponseInterceptor(errorInterceptor)
.addResponseInterceptor(loggingInterceptor);
// Use configured API
const users = productionAPI.get('/users');
const metrics = productionAPI.get('/analytics/metrics');
```
---
## ⚡ **FX Integration Patterns**
### **Direct FX Core Access**
```javascript
// Escape hatch to FX core's @/api/ syntax
const directNode = $api.fx('/users');
// This is equivalent to:
const directNode2 = $$('@/api/users');
// Both return FX nodes with reactive capabilities
directNode.watch(data => updateUI(data));
```
### **Reactive Data Binding**
```javascript
// API responses automatically become reactive FX nodes
const users = $api.get('/users');
// Bind to DOM using fx-dom-dollar
$('#user-count').text(users.length);
$('#user-list').html(users.map(u => `
${u.name}`).join(''));
// React to data changes
users.watch(userData => {
$$('app.stats.userCount').set(userData.length);
$$('app.cache.lastUserFetch').set(Date.now());
});
```
### **Complex API Workflows**
```javascript
// Multi-step API workflows with FX reactivity
function createUserWorkflow(userData) {
// Step 1: Validate user data
const validation = $api.post('/validate/user', userData);
validation.watch(result => {
if (result.valid) {
// Step 2: Create user
const newUser = $api.post('/users', userData);
newUser.watch(user => {
// Step 3: Send welcome email
$api.post('/emails/welcome', { userId: user.id });
// Step 4: Update app state
$$('app.currentUser').set(user);
$$('notifications.success').set('User created successfully!');
});
} else {
$$('notifications.error').set(result.errors);
}
});
}
```
---
## 🎪 **Real-World Examples**
### **Authentication Flow**
```javascript
// Login system with reactive state management
class AuthAPI {
static login(credentials) {
const response = $api.post('/auth/login', credentials);
response.watch(result => {
if (result.success) {
$$('auth.token').set(result.token);
$$('auth.user').set(result.user);
$$('auth.isLoggedIn').set(true);
// Redirect to dashboard
$$('router.currentPath').set('/dashboard');
} else {
$$('auth.error').set(result.message);
}
});
return response;
}
static logout() {
const response = $api.post('/auth/logout');
response.watch(() => {
// Clear auth state
$$('auth.token').set(null);
$$('auth.user').set(null);
$$('auth.isLoggedIn').set(false);
// Redirect to login
$$('router.currentPath').set('/login');
});
return response;
}
static refreshToken() {
const currentToken = $$('auth.token').get();
const response = $api.post('/auth/refresh', { token: currentToken });
response.watch(result => {
if (result.token) {
$$('auth.token').set(result.token);
} else {
// Token refresh failed, logout user
this.logout();
}
});
return response;
}
}
// Usage - completely synchronous from user perspective
AuthAPI.login({ email: 'user@example.com', password: 'secret' });
// Auth state updates automatically, UI reacts immediately
```
### **Data Management with Caching**
```javascript
// Smart data fetching with automatic caching
class DataManager {
static getUsers(filters = {}) {
// Create cache-friendly endpoint
const endpoint = `/users?${new URLSearchParams(filters).toString()}`;
// Get data using FX API
const response = $api.get(endpoint);
// Set up reactive caching
response.watch(users => {
// Cache in FX for 5 minutes
$$('cache.users').set(users);
$$('cache.users.timestamp').set(Date.now());
// Update global user list
$$('app.users.list').set(users);
$$('app.users.count').set(users.length);
});
return response;
}
static getUserProfile(userId) {
// Check cache first
const cached = $$('cache.userProfiles')[userId];
const cacheAge = Date.now() - (cached?.timestamp || 0);
if (cached && cacheAge < 300000) { // 5 minutes
return cached.data;
}
// Fetch fresh data
const response = $api.get(`/users/${userId}/profile`);
response.watch(profile => {
// Cache profile data
$$(`cache.userProfiles.${userId}`).set({
data: profile,
timestamp: Date.now()
});
// Update current profile if it's the active user
if ($$('app.currentUser.id').get() === userId) {
$$('app.currentUser.profile').set(profile);
}
});
return response;
}
static createUser(userData) {
const response = $api.post('/users', userData);
response.watch(newUser => {
// Add to local users list
const currentUsers = $$('app.users.list').get() || [];
$$('app.users.list').set([...currentUsers, newUser]);
// Update count
$$('app.users.count').set(currentUsers.length + 1);
// Clear cache to force refresh
$$('cache.users').set(null);
// Show success notification
$$('notifications.success').set(`User ${newUser.name} created!`);
});
return response;
}
}
```
### **Real-Time API Integration**
```javascript
// Real-time dashboard with reactive API calls
class DashboardAPI {
static initialize() {
// Set up periodic data fetching
this.startMetricsUpdates();
this.startNotificationPolling();
this.setupWebSocketFallback();
}
static startMetricsUpdates() {
const updateMetrics = () => {
const metrics = $api.get('/dashboard/metrics');
metrics.watch(data => {
$$('dashboard.metrics').set(data);
$$('dashboard.lastUpdate').set(Date.now());
// Schedule next update
setTimeout(updateMetrics, 30000); // 30 seconds
});
};
updateMetrics();
}
static startNotificationPolling() {
const pollNotifications = () => {
const lastCheck = $$('notifications.lastCheck').get() || 0;
const notifications = $api.get(`/notifications?since=${lastCheck}`);
notifications.watch(newNotifications => {
if (newNotifications.length > 0) {
const current = $$('notifications.list').get() || [];
$$('notifications.list').set([...current, ...newNotifications]);
$$('notifications.unreadCount').set(
current.length + newNotifications.length
);
}
$$('notifications.lastCheck').set(Date.now());
// Schedule next poll
setTimeout(pollNotifications, 10000); // 10 seconds
});
};
pollNotifications();
}
static setupWebSocketFallback() {
// If WebSocket connection fails, fall back to API polling
$$('websocket.connected').watch(connected => {
if (!connected) {
console.log('WebSocket down, using API fallback');
this.startMetricsUpdates();
}
});
}
}
```
---
## 🚨 **Best Practices & Patterns**
### ✅ **DO: Leverage FX's Synchronous Surface**
```javascript
// ✅ Direct usage - feels synchronous
const users = $api.get('/users');
const user = $api.get('/users/42');
// ✅ Immediate reactive binding
users.watch(data => updateUserList(data));
$('#user-count').text(users.length); // Updates automatically
// ✅ Chain API calls reactively
const profile = $api.get('/users/42/profile');
profile.watch(data => {
if (data.preferences) {
const settings = $api.get('/users/42/settings');
settings.watch(settingsData => {
$$('user.completeProfile').set({ ...data, ...settingsData });
});
}
});
```
### ❌ **DON'T: Use Async/Await (Breaks FX Principles)**
```javascript
// ❌ DON'T: Manual async handling
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
updateUI(user);
} catch (error) {
handleError(error);
}
}
// ✅ DO: Use FX API for immediate reactive results
function loadUser(id) {
const user = $api.get(`/users/${id}`);
user.watch(userData => updateUI(userData));
// No try/catch needed - FX handles errors internally
}
```
### ✅ **DO: Use Interceptors for Cross-Cutting Concerns**
```javascript
// ✅ Authentication interceptor
const authAPI = $api.addRequestInterceptor((config) => {
const token = $$('auth.token').get();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// ✅ Logging interceptor
const loggedAPI = authAPI.addResponseInterceptor((response, config) => {
$$('api.logs').set({
endpoint: config.url,
method: config.method,
timestamp: Date.now(),
status: response.status || 'success'
});
return response;
});
```
---
## 🎯 **Comparison: FX API vs Raw FX Core**
### **Raw FX Core (Built-in)**
```javascript
// Direct FX core usage
const users = $$('@/api/users').get({
headers: { 'Authorization': 'Bearer token' }
});
const newUser = $$('@/api/users').post({
body: { name: 'John' },
headers: { 'Content-Type': 'application/json' }
});
```
### **FX API Plugin (Convenience)**
```javascript
// Same functionality, cleaner syntax
const users = $api.get('/users'); // Headers from config
const newUser = $api.post('/users', { name: 'John' }); // Body simplified
```
### **Both Are Valid!**
- **Use FX core directly** for simple cases or when you need full control
- **Use FX API plugin** for complex applications with consistent API patterns
- **Mix both approaches** - they're fully compatible!
---
## 🛠 **Integration with Other FX Plugins**
### **With fx-dom-dollar**
```javascript
// API data binding to DOM
const users = $api.get('/users');
// Bind API response to DOM elements
$('#user-count').text(users.length);
$('#loading').css('display', users.loading ? 'block' : 'none');
// Form submission using API
$('#user-form').on('submit', (e) => {
e.preventDefault();
const formData = {
name: $('#name').val(),
email: $('#email').val()
};
const response = $api.post('/users', formData);
response.watch(result => {
if (result.success) {
$('#message').text('User created successfully!');
$('#user-form')[0].reset();
} else {
$('#error').text(result.message);
}
});
});
```
### **With fx-orm**
```javascript
// Hybrid local/remote data management
class UserService {
static syncUserData(userId) {
// Get user from API
const apiUser = $api.get(`/users/${userId}`);
apiUser.watch(userData => {
// Update local database
const localUser = $db.users.find(`#${userId}`);
if (localUser) {
localUser.name = userData.name;
localUser.email = userData.email;
localUser.lastSync = Date.now();
} else {
$db.users.create(userData);
}
// Update FX state
$$('app.currentUser').set(userData);
});
}
static saveUserChanges(userId, changes) {
// Update API
const apiResponse = $api.patch(`/users/${userId}`, changes);
// Update local DB immediately (optimistic update)
const localUser = $db.users.find(`#${userId}`);
Object.assign(localUser, changes);
apiResponse.watch(result => {
if (!result.success) {
// Rollback on API failure
const originalData = $api.get(`/users/${userId}`);
originalData.watch(original => {
Object.assign(localUser, original);
});
}
});
}
}
```
### **With fx-flow**
```javascript
// API calls in FX flows
$flow('userRegistration')
.node('validateEmail', {
runsOn: 'client',
effect: (ctx) => {
const validation = $api.post('/validate/email', { email: ctx.in.email });
validation.watch(result => {
if (result.valid) {
ctx.next('createUser', ctx.in);
} else {
ctx.error('Email validation failed');
}
});
}
})
.node('createUser', {
runsOn: 'server',
effect: (ctx) => {
const newUser = $api.post('/users', ctx.in);
newUser.watch(user => {
ctx.next('sendWelcome', { userId: user.id });
});
}
})
.node('sendWelcome', {
runsOn: 'both',
effect: (ctx) => {
const welcome = $api.post('/emails/welcome', ctx.in);
welcome.watch(() => {
$$('notifications.success').set('Registration complete!');
ctx.complete();
});
}
});
```
---
## 🎊 **Complete Application Example**
```javascript
// E-commerce API integration
class ShopAPI {
static initialize() {
// Configure API with authentication
this.api = $api
.base('https://shop-api.example.com/v1')
.addRequestInterceptor(this.addAuth)
.addResponseInterceptor(this.handleErrors);
}
static addAuth(config) {
const token = $$('auth.token').get();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
static handleErrors(response, config) {
if (response.status >= 400) {
$$('ui.errors').set({
message: response.message,
endpoint: config.url,
timestamp: Date.now()
});
}
return response;
}
// Product catalog
static getProducts(category = 'all', page = 1) {
const products = this.api.get(`/products?category=${category}&page=${page}`);
products.watch(data => {
$$('shop.products.list').set(data.products);
$$('shop.products.total').set(data.total);
$$('shop.products.currentPage').set(page);
});
return products;
}
// Shopping cart
static addToCart(productId, quantity = 1) {
const response = this.api.post('/cart/items', { productId, quantity });
response.watch(result => {
if (result.success) {
// Update cart state
const cartItems = $$('shop.cart.items').get() || [];
cartItems.push(result.item);
$$('shop.cart.items').set(cartItems);
$$('shop.cart.count').set(cartItems.length);
$$('shop.cart.total').set(
cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0)
);
// Show success message
$$('ui.notifications').set({
type: 'success',
message: 'Added to cart!',
timestamp: Date.now()
});
}
});
return response;
}
// Checkout process
static checkout(orderData) {
const response = this.api.post('/orders', orderData);
response.watch(result => {
if (result.success) {
// Clear cart
$$('shop.cart.items').set([]);
$$('shop.cart.count').set(0);
$$('shop.cart.total').set(0);
// Update order history
const orders = $$('user.orders').get() || [];
$$('user.orders').set([result.order, ...orders]);
// Redirect to confirmation
$$('router.currentPath').set(`/orders/${result.order.id}/confirmation`);
}
});
return response;
}
}
// Initialize and use
ShopAPI.initialize();
// Reactive product loading
$$('ui.selectedCategory').watch(category => {
ShopAPI.getProducts(category);
});
// Reactive cart updates
$$('shop.cart.count').watch(count => {
$('#cart-badge').text(count);
$('#cart-badge').css('display', count > 0 ? 'block' : 'none');
});
```
---
This cheat sheet demonstrates how FX API provides convenience while maintaining FX's core principles of synchronous operation and reactive data management. The plugin seamlessly integrates with the FX ecosystem while providing familiar HTTP API patterns.