Package Exports
- exguard-client
- exguard-client/setup
Readme
exguard-client
ExGuard RBAC (Role-Based Access Control) client library with cache-first Redis support for EmpowerX applications.
๐ Cache-First Performance: Prioritizes Redis cache over API calls for maximum speed and efficiency.
Features
- ๐ RBAC Authentication: Token verification and user access management
- ๐ Realtime Updates: WebSocket-based real-time RBAC changes
- ๐ฏ Permission Guards: React components for route and feature protection
- ๐ช React Hooks: Easy-to-use hooks for permission checking
- ๐ฆ TypeScript: Full type safety with TypeScript definitions
- โก Auto-Configuration: Automatically detects API URL based on environment
- ๐ Zero Setup: Works out of the box with sensible defaults
- ๐พ Cache-First Redis: Prioritizes Redis cache, minimizes API calls
- ๐ Cross-Tab Sync: Synchronized data across browser tabs
- โก Backend Integration: Seamless integration with exguard-cached backend package
Cache-First Strategy
This package implements a pure cache-first approach:
- โ Always check Redis cache first
- ๐ก Call API ONLY if cache is completely empty
- ๐ซ Never call API for permission checks
- ๐ Rely on backend cache invalidation for updates
Prerequisites
Required Dependencies
pnpm add react react-dom react-router axios socket.io-clientRedis Setup
You need a Redis server running and accessible from your frontend application:
Option 1: Direct Redis Connection
# Install Redis server
# Ubuntu/Debian: sudo apt-get install redis-server
# macOS: brew install redis
# Windows: Use Docker or WSL
# Start Redis server
redis-serverOption 2: Redis Proxy (Recommended for Production)
Set up a WebSocket/HTTP proxy to Redis for browser compatibility.
Environment Variables
Create a .env file in your project root:
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
REDIS_DB=0
REDIS_KEY_PREFIX=exguard:frontend:
REDIS_TLS=false
# ExGuard API Configuration
EXGUARD_API_URL=http://localhost:3000Installation
pnpm add exguard-clientQuick Start
1. Initialize Redis Client
Add this to your app setup (e.g., App.tsx or main.tsx):
import { initializeCacheFirstRedisClient } from 'exguard-client';
// Initialize once at app startup
initializeCacheFirstRedisClient({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: parseInt(process.env.REDIS_DB || '0'),
keyPrefix: 'exguard:frontend:',
tls: process.env.REDIS_TLS === 'true',
});2. Wrap with Realtime Provider
import { ExGuardRealtimeProvider } from 'exguard-client';
function App() {
return (
<ExGuardRealtimeProvider>
<Router>
<Routes>
<Route path="/protected" element={<ProtectedRoute />} />
</Routes>
</Router>
</ExGuardRealtimeProvider>
);
}3. Use Cache-First Hook
import { useUserAccessCacheFirst } from 'exguard-client';
function ProtectedRoute() {
const { userAccess, isLoading, hasPermission } = useUserAccessCacheFirst();
// Permission checking uses ONLY cached data - no API calls!
const canViewUsers = hasPermission('exGUARD', 'users:view');
const canEditRoles = hasPermission('exGUARD', 'roles:edit');
if (isLoading) return <div>Loading from cache...</div>;
return (
<div>
<h1>Welcome {userAccess?.user?.username}</h1>
{canViewUsers && <UserManagement />}
{canEditRoles && <RoleEditor />}
</div>
);
}Cache-First Hook API
useUserAccessCacheFirst()
Returns an object with the following properties:
interface UseUserAccessCacheFirstReturn {
userAccess: UserAccessData | null; // Cached user data
isLoading: boolean; // Loading from cache
isFetching: boolean; // Fetching from API (rare)
error: Error | null; // Error state
hasPermission: (module: string, permission: string) => boolean;
hasModuleAccess: (module: string) => boolean;
getModulePermissions: (module: string) => string[];
refetch: (force?: boolean) => Promise<void>; // Force API call
refetchSilent: () => Promise<void>; // Silent refresh
invalidateCache: () => void; // Invalidate cache
clearCache: () => Promise<void>; // Clear cache
}Permission Checking
const { hasPermission, hasModuleAccess } = useUserAccessCacheFirst();
// Check specific permission (uses cached data only)
const canView = hasPermission('exGUARD', 'users:view');
// Check module access
const hasUserModule = hasModuleAccess('exGUARD');
// Get all permissions for a module
const permissions = getModulePermissions('exGUARD');Cache Behavior
When API is Called ๐ก
The API endpoint is called ONLY in these rare cases:
- First visit with completely empty cache
- Cache expired and real-time update invalidates
- Manual refetch is explicitly called
- Cache corruption detected
When API is NOT Called ๐ซ
The API is NEVER called for:
- Permission checks - uses cached data only
- Module access checks - uses cached data only
- User roles/groups - uses cached data only
- Field office data - uses cached data only
Cache Performance
// โ
INSTANT - Uses cached data (0ms)
const canView = hasPermission('exGUARD', 'users:view');
// ๐ก SLOW - API call only when cache empty
// (happens once per session or on cache invalidation)
const { userAccess } = useUserAccessCacheFirst();Backend Integration
For optimal performance, ensure your backend uses the exguard-cached package:
Backend Setup
# Install backend package
pnpm add exguard-cached
# Auto-configure (recommended)
npx exguard-cached setupBackend Endpoint
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RequireCachedUserPermission, CachedUser } from 'exguard-cached';
@Controller('guard')
export class GuardController {
@Get('me')
@RequireCachedUserPermission('exGUARD', 'profile:view')
async getMe(@CachedUser() cachedUser: CachedUserData) {
return {
success: true,
data: {
user: cachedUser.user,
groups: cachedUser.groups,
roles: cachedUser.roles,
modules: cachedUser.modules,
fieldOffices: cachedUser.fieldOffices,
}
};
}
}Cache Synchronization
The backend automatically invalidates cache when:
- User permissions change
- Roles are updated
- Groups are modified
- Real-time WebSocket updates are received
Advanced Usage
Direct Redis Client Access
import { getCacheFirstRedisClient } from 'exguard-client';
const redisClient = getCacheFirstRedisClient();
// Direct Redis operations
await redisClient.set('custom:key', data, { ttl: 300 });
const cached = await redisClient.get('custom:key');
await redisClient.del('custom:key');Custom Cache Keys
import { RedisCacheClient } from 'exguard-client';
const customClient = new RedisCacheClient({
host: 'localhost',
port: 6379,
keyPrefix: 'myapp:custom:',
});
await customClient.set('user:123', userData, { ttl: 600 });Fallback to LocalStorage
If Redis is unavailable, the client gracefully falls back to localStorage:
// The hook automatically handles Redis failures
const { userAccess } = useUserAccessCacheFirst();
// Works even if Redis is down (uses localStorage fallback)Migration Guide
From localStorage
// Before (localStorage)
import { useUserAccessSingleton } from 'exguard-client';
const { userAccess } = useUserAccessSingleton();
// After (Cache-First Redis)
import { useUserAccessCacheFirst, initializeCacheFirstRedisClient } from 'exguard-client';
// Initialize once
initializeCacheFirstRedisClient({
host: 'localhost',
port: 6379,
});
// Use in components - cache-first approach
const { userAccess, hasPermission } = useUserAccessCacheFirst();From Basic Redis
// Before (basic Redis)
import { useUserAccessRedis, initializeRedisClient } from 'exguard-client';
initializeRedisClient(config);
// After (Cache-First Redis)
import { useUserAccessCacheFirst, initializeCacheFirstRedisClient } from 'exguard-client';
initializeCacheFirstRedisClient(config);Performance Benefits
| Feature | localStorage | Basic Redis | Cache-First Redis |
|---|---|---|---|
| API Calls | Frequent | Moderate | Minimal |
| Cross-tab Sync | โ | โ | โ |
| Backend Sync | โ | โ | โ |
| Permission Speed | โก Fast | ๐ Fast | โกโก Instant |
| Cache Hits | 30% | 70% | 95%+ |
Troubleshooting
Common Issues
Redis Connection Failed
# Check Redis server
redis-cli ping
# Check configuration
echo $REDIS_HOST
echo $REDIS_PORTCache Not Updating
// Force cache refresh
const { refetch } = useUserAccessCacheFirst();
await refetch(true); // Force API callPermission Check Returns False
// Debug permission check
const { userAccess } = useUserAccessCacheFirst();
console.log('User modules:', userAccess?.modules);
console.log('Permissions:', getModulePermissions('exGUARD'));Debug Mode
Enable debug logging:
// Add to environment
EXGUARD_DEBUG=true
// Check browser console for cache logs
// [ExGuard Cache-First] โ
Cache hit - using Redis data
// [ExGuard Cache-First] ๐ก No cache found, calling API as last resortLicense
MIT License - see LICENSE file for details.
Support
For issues and questions:
- Check the troubleshooting section
- Verify Redis connectivity
- Ensure backend uses
exguard-cachedpackage - Check environment variables
๐ Cache-First Performance: Built for speed, designed for scale.