Skip to main content

API Integration Patterns

Overview

The MyTradeX platform implements a comprehensive RESTful API architecture with multiple integration patterns to support various client applications and external systems. This document outlines the API design principles, integration patterns, and best practices used throughout the platform.

API Architecture Principles

Core Design Principles

  • RESTful Design: RESTful endpoints following HTTP semantics
  • Resource-Based URLs: Clear, hierarchical resource naming
  • Stateless Communication: Each request contains all necessary information
  • Consistent Response Format: Standardized API response structure
  • Error Handling: Comprehensive error codes and messages
  • Versioning: API versioning for backward compatibility
  • Documentation: OpenAPI/Swagger specification for all endpoints
  • Security: JWT-based authentication and authorization

API Design Standards

Resource Naming Conventions:

// Resource-based URLs
GET /api/v1/users // Get all users
GET /api/v1/users/{id} // Get specific user
POST /api/v1/users // Create new user
PUT /api/v1/users/{id} // Update user
DELETE /api/v1/users/{id} // Delete user

// Trading-specific resources
GET /api/v1/orders // Get orders
POST /api/v1/orders // Create order
GET /api/v1/orders/{id} // Get specific order
PUT /api/v1/orders/{id} // Update order
DELETE /api/v1/orders/{id} // Cancel order

// Market data resources
GET /api/v1/market-data/{symbol} // Get market data
GET /api/v1/orderbook/{symbol} // Get order book
GET /api/v1/trades/{symbol} // Get trade history

Response Format Standard:

interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
pagination?: {
page: number;
size: number;
total: number;
totalPages: number;
};
timestamp: string;
requestId: string;
}

// Success response
{
"success": true,
"data": {
"id": "123",
"symbol": "AAPL",
"quantity": 100,
"price": 150.00
},
"timestamp": "2024-01-15T10:30:00Z",
"requestId": "req-abc-123"
}

// Error response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid order parameters",
"details": {
"field": "quantity",
"reason": "must be positive"
}
},
"timestamp": "2024-01-15T10:30:00Z",
"requestId": "req-abc-123"
}

Core API Endpoints

1. Authentication API

Base URL: /api/v1/auth

Login Endpoint

POST /api/v1/auth/login
Content-Type: application/json

Request:
{
"email": "user@example.com",
"password": "password123"
}

Response:
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "refresh-token-here",
"user": {
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"role": "TRADER"
},
"expiresIn": 3600
}
}

Refresh Token Endpoint

POST /api/v1/auth/refresh
Content-Type: application/json

Request:
{
"refreshToken": "refresh-token-here"
}

Response:
{
"success": true,
"data": {
"token": "new-jwt-token-here",
"expiresIn": 3600
}
}

Logout Endpoint

POST /api/v1/auth/logout
Authorization: Bearer {token}

Response:
{
"success": true,
"message": "Successfully logged out"
}

Current User Endpoint

GET /api/v1/auth/me
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"role": "TRADER",
"permissions": ["READ_MARKET_DATA", "CREATE_ORDERS"]
}
}

2. User Management API

Base URL: /api/v1/users

Get All Users (Admin Only)

GET /api/v1/users?page=1&size=20&role=TRADER&status=ACTIVE
Authorization: Bearer {token}

Response:
{
"success": true,
"data": [
{
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"role": "TRADER",
"status": "ACTIVE",
"createdAt": "2024-01-01T00:00:00Z",
"lastLogin": "2024-01-15T09:30:00Z"
}
],
"pagination": {
"page": 1,
"size": 20,
"total": 100,
"totalPages": 5
}
}

Create User (Admin Only)

POST /api/v1/users
Authorization: Bearer {adminToken}

Request:
{
"email": "newuser@example.com",
"name": "Jane Doe",
"role": "TRADER",
"password": "tempPassword123"
}

Response:
{
"success": true,
"data": {
"id": "user-456",
"email": "newuser@example.com",
"name": "Jane Doe",
"role": "TRADER",
"status": "ACTIVE",
"createdAt": "2024-01-15T10:30:00Z"
}
}

Update User (Admin Only)

PUT /api/v1/users/{userId}
Authorization: Bearer {adminToken}

Request:
{
"name": "Jane Smith",
"role": "ADMIN",
"status": "SUSPENDED"
}

Response:
{
"success": true,
"data": {
"id": "user-456",
"email": "newuser@example.com",
"name": "Jane Smith",
"role": "ADMIN",
"status": "SUSPENDED",
"updatedAt": "2024-01-15T11:00:00Z"
}
}

3. Order Management API

Base URL: /api/v1/orders

Create Order

POST /api/v1/orders
Authorization: Bearer {token}

Request:
{
"symbol": "AAPL",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 100,
"price": 150.00,
"timeInForce": "DAY"
}

Response:
{
"success": true,
"data": {
"id": "order-123",
"userId": "user-123",
"symbol": "AAPL",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 100,
"price": 150.00,
"remainingQuantity": 100,
"status": "NEW",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
}

Get User Orders

GET /api/v1/orders?status=NEW&symbol=AAPL&page=1&size=20
Authorization: Bearer {token}

Response:
{
"success": true,
"data": [
{
"id": "order-123",
"symbol": "AAPL",
"side": "BUY",
"quantity": 100,
"filledQuantity": 50,
"remainingQuantity": 50,
"price": 150.00,
"status": "PARTIAL",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:45:00Z"
}
],
"pagination": {
"page": 1,
"size": 20,
"total": 50,
"totalPages": 3
}
}

Get Specific Order

GET /api/v1/orders/{orderId}
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"id": "order-123",
"symbol": "AAPL",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 100,
"price": 150.00,
"filledQuantity": 100,
"remainingQuantity": 0,
"averageFillPrice": 149.50,
"status": "FILLED",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:45:00Z",
"trades": [
{
"id": "trade-456",
"quantity": 50,
"price": 149.50,
"timestamp": "2024-01-15T10:35:00Z"
}
]
}
}

Cancel Order

DELETE /api/v1/orders/{orderId}
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"id": "order-123",
"status": "CANCELLED",
"updatedAt": "2024-01-15T11:00:00Z"
}
}

4. Market Data API

Base URL: /api/v1/market-data

Get Market Data for Symbol

GET /api/v1/market-data/AAPL
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"symbol": "AAPL",
"lastPrice": 150.25,
"bidPrice": 150.20,
"askPrice": 150.30,
"bidQuantity": 1000,
"askQuantity": 800,
"volume": 1000000,
"high24h": 152.00,
"low24h": 149.50,
"change24h": 2.25,
"changePercent24h": 1.52,
"timestamp": "2024-01-15T10:30:00Z"
}
}

Get Multiple Symbols

POST /api/v1/market-data/batch
Authorization: Bearer {token}

Request:
{
"symbols": ["AAPL", "GOOGL", "MSFT"]
}

Response:
{
"success": true,
"data": [
{
"symbol": "AAPL",
"lastPrice": 150.25,
// ... other fields
},
{
"symbol": "GOOGL",
"lastPrice": 2750.00,
// ... other fields
}
]
}

Get Order Book

GET /api/v1/orderbook/AAPL?depth=10
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"symbol": "AAPL",
"timestamp": "2024-01-15T10:30:00Z",
"bids": [
[150.20, 1000],
[150.15, 1500],
[150.10, 2000]
],
"asks": [
[150.30, 800],
[150.35, 1200],
[150.40, 1600]
]
}
}

5. Portfolio API

Base URL: /api/v1/portfolio

Get User Positions

GET /api/v1/portfolio/positions
Authorization: Bearer {token}

Response:
{
"success": true,
"data": [
{
"id": "position-123",
"symbol": "AAPL",
"quantity": 100,
"averageCost": 149.50,
"currentPrice": 150.25,
"unrealizedPnL": 75.00,
"realizedPnL": 125.00,
"lastUpdated": "2024-01-15T10:30:00Z"
}
]
}

Get Portfolio Summary

GET /api/v1/portfolio/summary
Authorization: Bearer {token}

Response:
{
"success": true,
"data": {
"totalValue": 150250.00,
"totalUnrealizedPnL": 150.00,
"totalRealizedPnL": 125.00,
"dayPnL": 25.00,
"positions": [
{
"symbol": "AAPL",
"quantity": 100,
"marketValue": 15025.00,
"pnL": 75.00
}
]
}
}

Integration Patterns

1. Frontend Integration

React Query Hooks Integration

Location: mytradex-monorepo/packages/hooks/src/useOrders.ts

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@mytradex/api';
import type { Order, CreateOrderRequest } from '@mytradex/types';

// Hook for fetching orders
export const useOrders = (filters?: OrderFilters) => {
return useQuery({
queryKey: ['orders', filters],
queryFn: async () => {
const response = await apiClient.get<Order[]>('/orders', {
params: filters
});
return response.data;
},
staleTime: 30000, // 30 seconds
cacheTime: 300000, // 5 minutes
});
};

// Hook for creating orders
export const useCreateOrder = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async (orderData: CreateOrderRequest): Promise<Order> => {
const response = await apiClient.post<Order>('/orders', orderData);
return response.data;
},
onSuccess: (newOrder) => {
// Update orders cache
queryClient.setQueryData(['orders'], (old: Order[] | undefined) => {
return old ? [newOrder, ...old] : [newOrder];
});

// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ['portfolio'] });
},
onError: (error) => {
console.error('Failed to create order:', error);
},
});
};

// Hook for canceling orders
export const useCancelOrder = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async (orderId: string): Promise<Order> => {
const response = await apiClient.delete<Order>(`/orders/${orderId}`);
return response.data;
},
onSuccess: (cancelledOrder) => {
// Update orders cache
queryClient.setQueryData(['orders'], (old: Order[] | undefined) => {
return old?.map(order =>
order.id === cancelledOrder.id ? cancelledOrder : order
);
});
},
});
};

Custom Hook Integration

Location: mytradex-monorepo/packages/hooks/src/useMarketData.ts

import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@mytradex/api';
import { getSocket } from '@mytradex/api';

export const useMarketData = (symbol: string) => {
return useQuery({
queryKey: ['market-data', symbol],
queryFn: async () => {
const response = await apiClient.get(`/market-data/${symbol}`);
return response.data;
},
enabled: !!symbol,
staleTime: 5000, // 5 seconds for market data
refetchInterval: 5000, // Auto-refetch every 5 seconds
});
};

// Hook for real-time market data
export const useMarketDataRealtime = (symbol: string) => {
const [data, setData] = useState(null);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
const socket = getSocket();

const handleConnect = () => {
setIsConnected(true);
socket.emit('subscribe', `market:${symbol}`);
};

const handleDisconnect = () => {
setIsConnected(false);
};

const handleMarketData = (marketData: any) => {
if (marketData.symbol === symbol) {
setData(marketData);
}
};

socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
socket.on('market:update', handleMarketData);

return () => {
socket.off('connect', handleConnect);
socket.off('disconnect', handleDisconnect);
socket.off('market:update', handleMarketData);
socket.emit('unsubscribe', `market:${symbol}`);
};
}, [symbol]);

return { data, isConnected };
};

2. API Client Configuration

Centralized API Client

Location: mytradex-monorepo/packages/api/src/apiClient.ts

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

export class ApiClient {
private client: AxiosInstance;
private baseURL: string;

constructor(config: ApiConfig) {
this.baseURL = config.baseURL;
this.client = axios.create({
baseURL: config.baseURL,
timeout: config.timeout || 10000,
headers: {
'Content-Type': 'application/json',
...config.headers,
},
});

this.setupInterceptors();
this.setupRetryLogic();
this.setupRateLimiting();
}

private setupInterceptors() {
// Request interceptor
this.client.interceptors.request.use(
(config) => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

// Add request ID for tracking
config.headers['X-Request-ID'] = this.generateRequestId();

// Add timestamp
config.headers['X-Client-Timestamp'] = Date.now().toString();

return config;
},
(error) => Promise.reject(error)
);

// Response interceptor
this.client.interceptors.response.use(
(response) => {
// Log successful requests in development
if (process.env.NODE_ENV === 'development') {
console.log(`[API] ${response.config.method?.toUpperCase()} ${response.config.url}`, {
status: response.status,
duration: Date.now() - response.config.metadata?.startTime,
});
}

return response;
},
async (error) => {
const originalRequest = error.config;

// Handle 401 - Token refresh
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;

try {
const newToken = await this.refreshAuthToken();
if (newToken) {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return this.client(originalRequest);
}
} catch (refreshError) {
this.handleAuthFailure();
return Promise.reject(refreshError);
}
}

// Handle rate limiting
if (error.response?.status === 429) {
const retryAfter = parseInt(error.response.headers['retry-after']) || 60;
await this.delay(retryAfter * 1000);
return this.client(originalRequest);
}

return Promise.reject(this.enhanceError(error));
}
);
}

private setupRetryLogic() {
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const config = error.config;

if (!config || config.method === 'GET') {
return Promise.reject(error);
}

if (!config._retry) {
config._retry = true;

const shouldRetry = this.shouldRetry(error);
if (shouldRetry && config._retryCount < this.maxRetries) {
config._retryCount = (config._retryCount || 0) + 1;

const delay = this.calculateBackoffDelay(config._retryCount);
await this.delay(delay);

return this.client(config);
}
}

return Promise.reject(error);
}
);
}

private setupRateLimiting() {
const rateLimiter = new RateLimiter(100, 60000); // 100 requests per minute
this.client.interceptors.request.use(async (config) => {
await rateLimiter.acquire();
return config;
});
}

// HTTP methods
public async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return this.client.get<T>(url, config);
}

public async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return this.client.post<T>(url, data, config);
}

public async put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return this.client.put<T>(url, data, config);
}

public async patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return this.client.patch<T>(url, data, config);
}

public async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return this.client.delete<T>(url, config);
}

// Helper methods
private getAuthToken(): string | null {
return localStorage.getItem('authToken');
}

private async refreshAuthToken(): Promise<string | null> {
try {
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) return null;

const response = await axios.post(`${this.baseURL}/auth/refresh`, {
refreshToken
});

const { token } = response.data;
localStorage.setItem('authToken', token);

return token;
} catch (error) {
return null;
}
}

private handleAuthFailure() {
localStorage.removeItem('authToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
}

private shouldRetry(error: any): boolean {
return error.code === 'ECONNABORTED' ||
error.response?.status >= 500 ||
error.response?.status === 429;
}

private calculateBackoffDelay(retryCount: number): number {
return Math.min(1000 * Math.pow(2, retryCount), 30000);
}

private async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

private enhanceError(error: any) {
if (error.response?.data) {
return {
...error,
message: error.response.data.message || error.response.data.error,
code: error.response.data.code,
status: error.response.status,
};
}

return error;
}
}

// Default instance
export const apiClient = new ApiClient({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api',
timeout: 10000,
});

3. Error Handling Patterns

Global Error Handler

class APIErrorHandler {
static handle(error: any): AppError {
const { response, request, message } = error;

// Network errors
if (!response && request) {
return {
type: 'NETWORK_ERROR',
message: 'Network connection failed',
originalError: error,
};
}

// Server errors
if (response?.status >= 500) {
return {
type: 'SERVER_ERROR',
message: 'Server error occurred',
statusCode: response.status,
originalError: error,
};
}

// Client errors
if (response?.status >= 400) {
const errorData = response.data;

switch (response.status) {
case 400:
return {
type: 'VALIDATION_ERROR',
message: errorData.message || 'Invalid request data',
errors: errorData.details,
originalError: error,
};
case 401:
return {
type: 'UNAUTHORIZED',
message: 'Authentication required',
originalError: error,
};
case 403:
return {
type: 'FORBIDDEN',
message: 'Access denied',
originalError: error,
};
case 404:
return {
type: 'NOT_FOUND',
message: 'Resource not found',
originalError: error,
};
case 429:
return {
type: 'RATE_LIMITED',
message: 'Too many requests',
retryAfter: parseInt(response.headers['retry-after']),
originalError: error,
};
default:
return {
type: 'API_ERROR',
message: errorData.message || 'API request failed',
statusCode: response.status,
originalError: error,
};
}
}

// Generic error
return {
type: 'UNKNOWN_ERROR',
message: message || 'An unexpected error occurred',
originalError: error,
};
}

static handleAsyncError(error: any): never {
const appError = this.handle(error);
throw appError;
}
}

// Error boundary integration
export const withErrorBoundary = <T extends ComponentType<any>>(
Component: T,
fallback?: ComponentType<{ error: AppError }>
): T => {
return ((props: any) => (
<ErrorBoundary fallback={fallback}>
<Component {...props} />
</ErrorBoundary>
)) as T;
};

4. Pagination Patterns

Cursor-based Pagination

interface CursorPaginationParams {
cursor?: string;
limit?: number;
}

interface CursorPaginatedResponse<T> {
data: T[];
pagination: {
nextCursor?: string;
prevCursor?: string;
hasMore: boolean;
total?: number;
};
}

export const useCursorPaginatedQuery = <T>(
key: string[],
fetchFn: (params: CursorPaginationParams) => Promise<CursorPaginatedResponse<T>>,
params: CursorPaginationParams = {}
) => {
return useQuery({
queryKey: [...key, params],
queryFn: () => fetchFn(params),
keepPreviousData: true,
});
};

// Example usage with orders
export const useOrdersWithCursor = (params: CursorPaginationParams = {}) => {
return useCursorPaginatedQuery(
['orders', 'cursor'],
async ({ cursor, limit = 20 }) => {
const response = await apiClient.get('/orders', {
params: { cursor, limit }
});
return response.data;
},
params
);
};

// Infinite scroll component
export const InfiniteOrderList = () => {
const [cursor, setCursor] = useState<string | undefined>();

const {
data,
isLoading,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: ['orders', 'infinite'],
queryFn: ({ pageParam }) => apiClient.get('/orders', {
params: { cursor: pageParam, limit: 20 }
}).then(res => res.data),
getNextPageParam: (lastPage) => lastPage.pagination.nextCursor,
initialPageParam: undefined,
});

const orders = data?.pages.flatMap(page => page.data) || [];

return (
<div>
{orders.map(order => <OrderItem key={order.id} order={order} />)}

{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
};

5. Caching Strategies

Multi-level Caching

class CacheManager {
private memoryCache = new Map<string, CacheEntry>();
private readonly MEMORY_TTL = 5 * 60 * 1000; // 5 minutes
private readonly MAX_MEMORY_ENTRIES = 1000;

set(key: string, data: any, ttl?: number): void {
const entry: CacheEntry = {
data,
timestamp: Date.now(),
ttl: ttl || this.MEMORY_TTL
};

// Memory management
if (this.memoryCache.size >= this.MAX_MEMORY_ENTRIES) {
const firstKey = this.memoryCache.keys().next().value;
this.memoryCache.delete(firstKey);
}

this.memoryCache.set(key, entry);

// Persist to IndexedDB for longer storage
this.persistToStorage(key, data, ttl);
}

get(key: string): any | null {
// Check memory cache first
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry && this.isValid(memoryEntry)) {
return memoryEntry.data;
}

// Remove expired memory entry
if (memoryEntry) {
this.memoryCache.delete(key);
}

// Check persisted storage
return this.getFromStorage(key);
}

invalidate(key: string): void {
this.memoryCache.delete(key);
this.removeFromStorage(key);
}

invalidatePattern(pattern: string): void {
// Invalidate memory cache
const regex = new RegExp(pattern);
for (const cacheKey of this.memoryCache.keys()) {
if (regex.test(cacheKey)) {
this.memoryCache.delete(cacheKey);
}
}

// Invalidate persisted storage
this.invalidateStoragePattern(pattern);
}

private isValid(entry: CacheEntry): boolean {
return Date.now() - entry.timestamp < entry.ttl;
}

private async persistToStorage(key: string, data: any, ttl?: number): Promise<void> {
try {
const cacheData = {
data,
timestamp: Date.now(),
ttl: ttl || this.MEMORY_TTL
};

await indexedDB.set(key, cacheData);
} catch (error) {
console.warn('Failed to persist cache to storage:', error);
}
}

private async getFromStorage(key: string): Promise<any | null> {
try {
const cacheData = await indexedDB.get(key);
if (cacheData && this.isValid(cacheData)) {
// Restore to memory cache
this.memoryCache.set(key, cacheData);
return cacheData.data;
}
} catch (error) {
console.warn('Failed to retrieve cache from storage:', error);
}

return null;
}

private async removeFromStorage(key: string): Promise<void> {
try {
await indexedDB.delete(key);
} catch (error) {
console.warn('Failed to remove cache from storage:', error);
}
}

private async invalidateStoragePattern(pattern: string): Promise<void> {
try {
// Implementation for clearing IndexedDB entries matching pattern
const keys = await indexedDB.getAllKeys();
const regex = new RegExp(pattern);

for (const key of keys) {
if (regex.test(key as string)) {
await indexedDB.delete(key as string);
}
}
} catch (error) {
console.warn('Failed to invalidate storage pattern:', error);
}
}
}

interface CacheEntry {
data: any;
timestamp: number;
ttl: number;
}

// Global cache instance
export const cacheManager = new CacheManager();

Smart Cache Invalidation

class SmartCacheInvalidator {
private static readonly DEPENDENCIES = {
'orders': ['portfolio', 'positions', 'market-data'],
'market-data': ['positions', 'orderbook'],
'portfolio': ['positions'],
};

static invalidateDependencies(changedResource: string): void {
const dependencies = this.DEPENDENCIES[changedResource];

if (dependencies) {
dependencies.forEach(dep => {
this.invalidateByPattern(`${dep}:*`);
});
}

// Always invalidate the resource itself
this.invalidateByPattern(`${changedResource}:*`);
}

private static invalidateByPattern(pattern: string): void {
cacheManager.invalidatePattern(pattern);
queryClient.invalidateQueries({ queryKey: [pattern.split(':')[0]] });
}

// Optimistic updates with cache update
static optimisticUpdate<T>(
queryKey: string[],
updateFn: (oldData: T) => T,
mutationFn: () => Promise<T>
): Promise<T> {
return queryClient.executeMutation({
mutationFn,
onMutate: async () => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey });

// Snapshot previous value
const previousData = queryClient.getQueryData<T>(queryKey);

// Optimistically update
queryClient.setQueryData<T>(queryKey, (old) =>
old ? updateFn(old) : old
);

return { previousData };
},
onError: (error, variables, context) => {
// Rollback on error
if (context?.previousData) {
queryClient.setQueryData(queryKey, context.previousData);
}
},
onSettled: (data, error) => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey });

if (error) {
console.error('Mutation failed:', error);
}
},
});
}
}

6. File Upload Integration

File Upload with Progress

export const useFileUpload = () => {
const [progress, setProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);

const uploadFile = async (file: File, onProgress?: (progress: number) => void): Promise<string> => {
setIsUploading(true);
setProgress(0);

const formData = new FormData();
formData.append('file', file);

try {
const response = await apiClient.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
setProgress(progress);
onProgress?.(progress);
}
},
});

return response.data.fileId;
} finally {
setIsUploading(false);
}
};

return {
uploadFile,
progress,
isUploading,
};
};

// Component usage
export const DocumentUpload = () => {
const { uploadFile, progress, isUploading } = useFileUpload();

const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;

try {
const fileId = await uploadFile(file, (progress) => {
console.log(`Upload progress: ${progress}%`);
});

console.log(`File uploaded with ID: ${fileId}`);
} catch (error) {
console.error('Upload failed:', error);
}
};

return (
<div>
<input
type="file"
onChange={handleFileSelect}
disabled={isUploading}
/>

{isUploading && (
<div>
<p>Uploading... {progress}%</p>
<div style={{ width: '100%', background: '#f0f0f0', borderRadius: '4px' }}>
<div
style={{
width: `${progress}%`,
height: '20px',
background: '#007bff',
borderRadius: '4px',
transition: 'width 0.3s ease'
}}
/>
</div>
</div>
)}
</div>
);
};

Performance Optimization

1. Request Batching

class RequestBatcher {
private batch: Array<{ url: string; config?: any; resolve: Function; reject: Function }> = [];
private batchTimeout: NodeJS.Timeout | null = null;
private readonly batchSize = 10;
private readonly batchDelay = 100; // ms

async request(url: string, config?: any): Promise<any> {
return new Promise((resolve, reject) => {
this.batch.push({ url, config, resolve, reject });

if (this.batch.length >= this.batchSize) {
this.flushBatch();
} else if (!this.batchTimeout) {
this.batchTimeout = setTimeout(() => this.flushBatch(), this.batchDelay);
}
});
}

private flushBatch(): void {
if (this.batch.length === 0) return;

const currentBatch = this.batch.splice(0, this.batchSize);
this.batchTimeout = null;

// Process batch
Promise.allSettled(
currentBatch.map(({ url, config, resolve, reject }) =>
apiClient.get(url, config)
.then(response => resolve(response))
.catch(error => reject(error))
)
).then((results) => {
results.forEach((result, index) => {
const { resolve, reject } = currentBatch[index];
if (result.status === 'fulfilled') {
resolve(result.value);
} else {
reject(result.reason);
}
});
});
}
}

export const batchedApiClient = new RequestBatcher();

2. Data Prefetching

export const usePrefetchQueries = () => {
const queryClient = useQueryClient();

const prefetchMarketData = useCallback((symbols: string[]) => {
symbols.forEach(symbol => {
queryClient.prefetchQuery({
queryKey: ['market-data', symbol],
queryFn: () => apiClient.get(`/market-data/${symbol}`).then(res => res.data),
staleTime: 5 * 60 * 1000, // 5 minutes
});
});
}, [queryClient]);

const prefetchUserOrders = useCallback((userId: string) => {
queryClient.prefetchQuery({
queryKey: ['orders', userId],
queryFn: () => apiClient.get('/orders', {
params: { userId, limit: 50 }
}).then(res => res.data),
staleTime: 2 * 60 * 1000, // 2 minutes
});
}, [queryClient]);

return { prefetchMarketData, prefetchUserOrders };
};

// Component that prefetches data on hover/focus
export const QuickOrderButton = ({ symbol }: { symbol: string }) => {
const { prefetchMarketData } = usePrefetchQueries();

const handleMouseEnter = useCallback(() => {
prefetchMarketData([symbol]);
}, [symbol, prefetchMarketData]);

return (
<button
onClick={() => openOrderForm(symbol)}
onMouseEnter={handleMouseEnter}
className="quick-order-btn"
>
Quick Order
</button>
);
};

Next: Deployment Architecture

Continue to Deployment Architecture for production deployment strategies and infrastructure management.