@54vie/api - API Client
Package for managing networking, API calls, caching, and real-time connections.
Installation
pnpm add @54vie/api
Quick Start
import { apiClient, useQuery, useMutation } from '@54vie/api';
// Simple GET request
const users = await apiClient.get('/users');
// With React hook
function UserList() {
const { data, isLoading, error } = useQuery({
key: ['users'],
fn: () => apiClient.get('/users'),
});
if (isLoading) return <Loading />;
return <FlatList data={data} />;
}
API Client
Basic Requests
import { apiClient } from '@54vie/api';
// GET
const users = await apiClient.get('/users');
const user = await apiClient.get('/users/123');
// POST
const newUser = await apiClient.post('/users', {
name: 'John',
email: 'john@example.com',
});
// PUT
await apiClient.put('/users/123', { name: 'Jane' });
// PATCH
await apiClient.patch('/users/123', { status: 'active' });
// DELETE
await apiClient.delete('/users/123');
Request Options
const data = await apiClient.get('/products', {
params: { category: 'electronics', page: 1 },
headers: { 'X-Custom-Header': 'value' },
timeout: 10000,
cache: true,
cacheTTL: 5 * 60 * 1000, // 5 minutes
});
File Upload
const formData = new FormData();
formData.append('file', {
uri: fileUri,
type: 'image/jpeg',
name: 'photo.jpg',
});
await apiClient.upload('/upload', formData, {
onProgress: (progress) => {
console.log(`Upload: ${progress.percent}%`);
},
});
React Hooks
useQuery
Fetch data with caching and refetch:
import { useQuery } from '@54vie/api';
function ProductList() {
const {
data,
isLoading,
error,
refetch,
isRefetching,
} = useQuery({
key: ['products', categoryId],
fn: () => apiClient.get(`/products?category=${categoryId}`),
staleTime: 5 * 60 * 1000, // Cache 5 minutes
refetchOnMount: true,
refetchOnFocus: true,
onSuccess: (data) => console.log('Loaded:', data),
onError: (error) => console.error('Error:', error),
});
return (
<FlatList
data={data}
refreshing={isRefetching}
onRefresh={refetch}
/>
);
}
useMutation
For operations that change data (POST, PUT, DELETE):
import { useMutation, invalidateQueries } from '@54vie/api';
function CreateProduct() {
const { mutate, isLoading, error } = useMutation({
fn: (data) => apiClient.post('/products', data),
onSuccess: (result) => {
Toast.success('Product created!');
invalidateQueries(['products']); // Refetch product list
},
onError: (error) => {
Toast.error(error.message);
},
});
const handleSubmit = () => {
mutate({ name, price, category });
};
return (
<Button onPress={handleSubmit} loading={isLoading}>
Create
</Button>
);
}
useInfiniteQuery
For pagination/infinite scroll:
import { useInfiniteQuery } from '@54vie/api';
function InfiniteList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
key: ['posts'],
fn: ({ pageParam = 1 }) =>
apiClient.get(`/posts?page=${pageParam}`),
getNextPageParam: (lastPage) => lastPage.nextPage,
});
// Flatten pages
const posts = data?.pages.flatMap(page => page.items) ?? [];
return (
<FlatList
data={posts}
onEndReached={() => hasNextPage && fetchNextPage()}
ListFooterComponent={
isFetchingNextPage ? <Loading /> : null
}
/>
);
}
Interceptors
Request Interceptor
import { apiClient } from '@54vie/api';
// Add auth token to all requests
apiClient.interceptors.request.use((config) => {
const token = TokenManager.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Response Interceptor
// Handle 401 errors globally
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Try refresh token
const refreshed = await TokenManager.refreshToken();
if (refreshed) {
// Retry original request
return apiClient.request(error.config);
}
// Redirect to login
navigation.navigate('Login');
}
throw error;
}
);
Caching
Cache Manager
import { ApiCacheManager } from '@54vie/api';
// Set cache
ApiCacheManager.set('user:123', userData, 5 * 60 * 1000);
// Get cache
const cached = ApiCacheManager.get('user:123');
// Invalidate
ApiCacheManager.invalidate('user:123');
ApiCacheManager.invalidateByPrefix('user:');
// Clear all
ApiCacheManager.clear();
Cache Strategies
import {
cacheFirst,
networkFirst,
staleWhileRevalidate,
} from '@54vie/api';
// Cache first - Use cache if available
const data = await cacheFirst('products', () =>
apiClient.get('/products')
);
// Network first - Always try network, fallback to cache
const data = await networkFirst('products', () =>
apiClient.get('/products')
);
// Stale while revalidate - Return cache immediately, update in background
const data = await staleWhileRevalidate('products', fetcher, {
onRevalidate: (newData) => {
console.log('Data updated:', newData);
},
});
WebSocket
Basic Usage
import { wsClient } from '@54vie/api';
// Connect
await wsClient.connect('wss://api.54vie.vn/ws');
// Subscribe to channel
wsClient.subscribe('chat:room-123', (message) => {
console.log('New message:', message);
});
// Send message
wsClient.send('chat:room-123', { text: 'Hello!' });
// Unsubscribe
wsClient.unsubscribe('chat:room-123');
// Disconnect
wsClient.disconnect();
useWebSocket Hook
import { useWebSocket, useChannel } from '@54vie/api';
function ChatRoom({ roomId }) {
const { isConnected, send } = useWebSocket();
const { messages } = useChannel(`chat:${roomId}`, {
onMessage: (msg) => setMessages(prev => [...prev, msg]),
});
const sendMessage = (text) => {
send(`chat:${roomId}`, { text, userId: currentUser.id });
};
return (
<View>
<FlatList data={messages} />
<MessageInput onSend={sendMessage} />
</View>
);
}
Offline Support
Offline Queue
import { useOffline, offlineQueue } from '@54vie/api';
function CreateOrder() {
const { isOnline } = useOffline();
const handleSubmit = async () => {
if (isOnline) {
await apiClient.post('/orders', orderData);
} else {
// Queue for later
offlineQueue.add({
method: 'POST',
url: '/orders',
data: orderData,
});
Toast.info('Order saved. Will sync when online.');
}
};
}
Auto Sync
import { offlineQueue } from '@54vie/api';
// Process queue when online
offlineQueue.process();
// Listen to queue changes
offlineQueue.onStatusChange((status) => {
console.log(`Queue: ${status.pending} pending, ${status.failed} failed`);
});
Error Handling
import {
ApiError,
getErrorMessage,
isRetryableError,
isAuthError,
} from '@54vie/api';
try {
await apiClient.post('/orders', data);
} catch (error) {
if (error instanceof ApiError) {
// Get localized message
const message = getErrorMessage(error.code);
if (isAuthError(error.code)) {
// Handle auth error
navigation.navigate('Login');
} else if (isRetryableError(error.code)) {
// Can retry
Toast.error(message + ' Tap to retry.');
} else {
Toast.error(message);
}
}
}
Configuration
import { configureApi } from '@54vie/api';
configureApi({
baseURL: 'https://api.54vie.vn/v1',
timeout: 30000,
headers: {
'X-App-Version': '1.0.0',
'X-Platform': Platform.OS,
},
retry: {
attempts: 3,
delay: 1000,
statusCodes: [408, 500, 502, 503, 504],
},
cache: {
enabled: true,
ttl: 5 * 60 * 1000,
},
});