# API Integration
Learn how to connect your Dynamic Framework application with backend services, handle authentication, and optimize API calls.
# Initial Setup
# HTTP Client
Dynamic Framework recommends using Axios for HTTP calls:
// src/services/api/client.js
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'https://api.mydigitalbank.com',
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
// Request interceptor
apiClient.interceptors.request.use(
(config) => {
// Add authentication token
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Add custom headers
config.headers['X-Request-ID'] = generateRequestId();
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor
apiClient.interceptors.response.use(
(response) => {
// Process successful response
return response.data;
},
(error) => {
// Centralized error handling
if (error.response?.status === 401) {
// Expired or invalid token
handleUnauthorized();
}
if (error.response?.status === 429) {
// Rate limiting
handleRateLimiting(error.response);
}
return Promise.reject(error);
}
);
export default apiClient;
# Authentication
# OAuth 2.0 / OpenID Connect
// src/services/auth/authService.js
import { UserManager, WebStorageStateStore } from 'oidc-client';
class AuthService {
constructor() {
this.userManager = new UserManager({
authority: process.env.REACT_APP_AUTH_URL,
client_id: process.env.REACT_APP_CLIENT_ID,
redirect_uri: `${window.location.origin}/callback`,
response_type: 'code',
scope: 'openid profile email api',
post_logout_redirect_uri: window.location.origin,
userStore: new WebStorageStateStore({ store: window.localStorage }),
automaticSilentRenew: true,
silent_redirect_uri: `${window.location.origin}/silent-renew.html`,
});
}
async login() {
return this.userManager.signinRedirect();
}
async handleCallback() {
return this.userManager.signinRedirectCallback();
}
async logout() {
return this.userManager.signoutRedirect();
}
async getUser() {
return this.userManager.getUser();
}
async getAccessToken() {
const user = await this.getUser();
return user?.access_token;
}
async refreshToken() {
return this.userManager.signinSilent();
}
}
export default new AuthService();
# JWT Token Management
// src/services/auth/tokenManager.js
class TokenManager {
constructor() {
this.tokenKey = 'auth_token';
this.refreshTokenKey = 'refresh_token';
this.expiryKey = 'token_expiry';
}
setTokens(accessToken, refreshToken, expiresIn) {
localStorage.setItem(this.tokenKey, accessToken);
localStorage.setItem(this.refreshTokenKey, refreshToken);
const expiry = new Date().getTime() + (expiresIn * 1000);
localStorage.setItem(this.expiryKey, expiry);
}
getAccessToken() {
return localStorage.getItem(this.tokenKey);
}
getRefreshToken() {
return localStorage.getItem(this.refreshTokenKey);
}
isTokenExpired() {
const expiry = localStorage.getItem(this.expiryKey);
if (!expiry) return true;
return new Date().getTime() > parseInt(expiry);
}
async refreshAccessToken() {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error('Failed to refresh token');
}
const data = await response.json();
this.setTokens(data.accessToken, data.refreshToken, data.expiresIn);
return data.accessToken;
}
clearTokens() {
localStorage.removeItem(this.tokenKey);
localStorage.removeItem(this.refreshTokenKey);
localStorage.removeItem(this.expiryKey);
}
}
export default new TokenManager();
# Data Services
# Account Service
// src/services/accounts/accountService.js
import apiClient from '../api/client';
class AccountService {
async getAccounts() {
return apiClient.get('/accounts');
}
async getAccountById(accountId) {
return apiClient.get(`/accounts/${accountId}`);
}
async getAccountBalance(accountId) {
return apiClient.get(`/accounts/${accountId}/balance`);
}
async getAccountTransactions(accountId, params = {}) {
return apiClient.get(`/accounts/${accountId}/transactions`, { params });
}
async updateAccount(accountId, data) {
return apiClient.put(`/accounts/${accountId}`, data);
}
async closeAccount(accountId, reason) {
return apiClient.post(`/accounts/${accountId}/close`, { reason });
}
}
export default new AccountService();
# Transfer Service
// src/services/transfers/transferService.js
import apiClient from '../api/client';
class TransferService {
async validateTransfer(data) {
return apiClient.post('/transfers/validate', data);
}
async createTransfer(data) {
return apiClient.post('/transfers', data);
}
async getTransferStatus(transferId) {
return apiClient.get(`/transfers/${transferId}/status`);
}
async getTransferHistory(params = {}) {
return apiClient.get('/transfers/history', { params });
}
async cancelTransfer(transferId, reason) {
return apiClient.post(`/transfers/${transferId}/cancel`, { reason });
}
async scheduleTransfer(data) {
return apiClient.post('/transfers/schedule', data);
}
}
export default new TransferService();
# Custom Hooks
# useApi Hook
// src/hooks/useApi.js
import { useState, useEffect, useCallback } from 'react';
export const useApi = (apiFunction, immediate = true) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(immediate);
const [error, setError] = useState(null);
const execute = useCallback(async (...params) => {
setLoading(true);
setError(null);
try {
const result = await apiFunction(...params);
setData(result);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, [apiFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { data, loading, error, execute, refetch: execute };
};
# useAccounts Hook
// src/hooks/useAccounts.js
import { useQuery, useMutation, useQueryClient } from 'react-query';
import accountService from '../services/accounts/accountService';
export const useAccounts = () => {
return useQuery(
'accounts',
() => accountService.getAccounts(),
{
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
}
);
};
export const useAccountBalance = (accountId) => {
return useQuery(
['account-balance', accountId],
() => accountService.getAccountBalance(accountId),
{
enabled: !!accountId,
refetchInterval: 30000, // Update every 30 seconds
}
);
};
export const useUpdateAccount = () => {
const queryClient = useQueryClient();
return useMutation(
({ accountId, data }) => accountService.updateAccount(accountId, data),
{
onSuccess: () => {
queryClient.invalidateQueries('accounts');
},
}
);
};
# Error Handling
# Global Error Handler
// src/services/api/errorHandler.js
class ErrorHandler {
constructor() {
this.errorHandlers = new Map();
this.setupDefaultHandlers();
}
setupDefaultHandlers() {
// Network errors
this.registerHandler('NetworkError', (error) => {
console.error('Network error:', error);
this.showNotification('Connection error. Please check your internet.');
});
// Validation errors
this.registerHandler('ValidationError', (error) => {
const messages = error.response?.data?.errors || [];
messages.forEach(msg => this.showNotification(msg, 'warning'));
});
// Server errors
this.registerHandler('ServerError', (error) => {
console.error('Server error:', error);
this.showNotification('Server error. Please try again later.');
});
// Rate limiting
this.registerHandler('RateLimitError', (error) => {
const retryAfter = error.response?.headers['retry-after'];
this.showNotification(`Too many requests. Try again in ${retryAfter} seconds.`);
});
}
registerHandler(errorType, handler) {
this.errorHandlers.set(errorType, handler);
}
handle(error) {
const errorType = this.getErrorType(error);
const handler = this.errorHandlers.get(errorType);
if (handler) {
handler(error);
} else {
this.handleUnknownError(error);
}
}
getErrorType(error) {
if (!error.response) return 'NetworkError';
const status = error.response.status;
if (status === 400) return 'ValidationError';
if (status === 429) return 'RateLimitError';
if (status >= 500) return 'ServerError';
return 'UnknownError';
}
handleUnknownError(error) {
console.error('Unknown error:', error);
this.showNotification('An unexpected error has occurred.');
}
showNotification(message, type = 'error') {
// Implement according to your notification system
console.log(`[${type}] ${message}`);
}
}
export default new ErrorHandler();
# Optimization and Caching
# React Query Setup
// src/config/queryClient.js
import { QueryClient } from 'react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
refetchOnWindowFocus: false,
refetchOnReconnect: 'always',
},
mutations: {
retry: 1,
},
},
});
# Cache with Service Worker
// src/serviceWorker/apiCache.js
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Cache only GET calls to the API
if (request.method === 'GET' && url.pathname.startsWith('/api/')) {
event.respondWith(
caches.open('api-cache-v1').then(async (cache) => {
const cachedResponse = await cache.match(request);
if (cachedResponse) {
// Return from cache and update in background
const fetchPromise = fetch(request).then((networkResponse) => {
cache.put(request, networkResponse.clone());
return networkResponse;
});
return cachedResponse;
}
// If not in cache, fetch
const networkResponse = await fetch(request);
cache.put(request, networkResponse.clone());
return networkResponse;
})
);
}
});
# WebSockets and Real Time
# Socket.io Integration
// src/services/realtime/socketService.js
import io from 'socket.io-client';
class SocketService {
constructor() {
this.socket = null;
this.listeners = new Map();
}
connect(token) {
this.socket = io(process.env.REACT_APP_SOCKET_URL, {
auth: { token },
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
this.socket.on('connect', () => {
console.log('Socket connected');
this.emit('subscribe', { channels: ['accounts', 'transactions'] });
});
this.socket.on('disconnect', (reason) => {
console.log('Socket disconnected:', reason);
});
this.setupEventListeners();
}
setupEventListeners() {
// Balance updates
this.on('balance:update', (data) => {
this.notifyListeners('balanceUpdate', data);
});
// Transaction notifications
this.on('transaction:new', (data) => {
this.notifyListeners('newTransaction', data);
});
// System alerts
this.on('alert:system', (data) => {
this.notifyListeners('systemAlert', data);
});
}
on(event, callback) {
if (this.socket) {
this.socket.on(event, callback);
}
}
emit(event, data) {
if (this.socket) {
this.socket.emit(event, data);
}
}
subscribe(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(callback);
}
unsubscribe(event, callback) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(callback);
}
}
notifyListeners(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => callback(data));
}
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
}
}
export default new SocketService();
# API Testing
# Mock Service Worker
// src/mocks/handlers.js
import { rest } from 'msw';
export const handlers = [
rest.get('/api/accounts', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
{
id: '1',
name: 'Checking Account',
balance: 125430.00,
currency: 'USD',
},
{
id: '2',
name: 'Savings Account',
balance: 45200.00,
currency: 'USD',
},
])
);
}),
rest.post('/api/transfers', (req, res, ctx) => {
const { amount } = req.body;
if (amount > 100000) {
return res(
ctx.status(400),
ctx.json({
error: 'Amount exceeds maximum limit',
})
);
}
return res(
ctx.status(201),
ctx.json({
id: 'transfer-123',
status: 'completed',
amount,
})
);
}),
];
# Best Practices
# 1. Security
- Never store tokens in localStorage for sensitive applications
- Use httpOnly cookies when possible
- Implement CSRF protection
- Validate all inputs on client and server
# 2. Performance
- Implement pagination for large lists
- Use debounce for searches
- Cache responses when appropriate
- Implement lazy loading of data
# 3. State Management
- Use React Query or SWR for server state
- Keep local state minimal
- Sync state with backend regularly
# 4. Monitoring
- Log all failed API calls
- Implement performance metrics
- Use correlation IDs for tracking