Best Practices
This guide covers best practices for integrating PocketDNS into your application.
Performance
API Request Optimization
Batch Operations
When possible, batch API operations to reduce latency:
javascript
// Instead of multiple individual requests
const domains = await Promise.all([
getDomainDetails(domainId1),
getDomainDetails(domainId2),
getDomainDetails(domainId3)
]);
// Use batch endpoint when available
const domains = await getBatchDomains([domainId1, domainId2, domainId3]);Caching User Sessions
Cache user sessions to avoid unnecessary API calls:
javascript
class SessionCache {
constructor(ttl = 20 * 60 * 1000) { // 20 minutes
this.cache = new Map();
this.ttl = ttl;
}
get(userIdentifier) {
const cached = this.cache.get(userIdentifier);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.session;
}
return null;
}
set(userIdentifier, session) {
this.cache.set(userIdentifier, {
session,
timestamp: Date.now()
});
}
clear(userIdentifier) {
this.cache.delete(userIdentifier);
}
}
const sessionCache = new SessionCache();Connection Pooling
Use connection pooling for high-traffic applications:
javascript
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30000,
maxSockets: 50
});
const fetch = require('node-fetch');
const pocketDNSFetch = (url, options = {}) => {
return fetch(url, { ...options, agent });
};Frontend Optimization
Lazy Loading
Load the PocketDNS iframe only when needed:
jsx
import React, { useState, lazy, Suspense } from 'react';
const PocketDNSEmbed = lazy(() => import('./PocketDNSEmbed'));
const DomainManagement = () => {
const [showEmbed, setShowEmbed] = useState(false);
return (
<div>
{!showEmbed ? (
<button onClick={() => setShowEmbed(true)}>
Manage Domains
</button>
) : (
<Suspense fallback={<div>Loading...</div>}>
<PocketDNSEmbed />
</Suspense>
)}
</div>
);
};Preloading Sessions
Preload user sessions for better UX:
javascript
// Preload session when user navigates to domain page
const preloadSession = async (userIdentifier) => {
if (!sessionCache.get(userIdentifier)) {
const session = await createUserSession(userIdentifier);
sessionCache.set(userIdentifier, session);
}
};User Experience
Loading States
Always provide clear loading states:
jsx
const PocketDNSEmbed = ({ userIdentifier }) => {
const [state, setState] = useState('loading'); // loading, ready, error
return (
<div className="pocketdns-container">
{state === 'loading' && (
<div className="loading-state">
<div className="spinner" />
<p>Setting up your domain interface...</p>
</div>
)}
{state === 'error' && (
<div className="error-state">
<p>Unable to load domain interface. Please try again.</p>
<button onClick={retry}>Retry</button>
</div>
)}
{state === 'ready' && (
<iframe src={loginUrl} {...iframeProps} />
)}
</div>
);
};Error Handling
Provide helpful error messages:
javascript
const handleAPIError = (error, context) => {
const errorMessages = {
401: 'Authentication failed. Please check your API key.',
403: 'Insufficient permissions for this operation.',
404: 'The requested resource was not found.',
429: 'Rate limit exceeded. Please try again later.',
500: 'Server error. Please try again later.'
};
const message = errorMessages[error.status] || 'An unexpected error occurred.';
console.error(`PocketDNS API Error (${context}):`, error);
return {
message,
status: error.status,
context,
timestamp: new Date().toISOString()
};
};Responsive Design
Ensure the embed works on all devices:
css
.pocketdns-embed {
width: 100%;
height: 600px;
min-height: 400px;
border: none;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.pocketdns-embed {
height: 500px;
border-radius: 4px;
}
}
@media (max-width: 480px) {
.pocketdns-embed {
height: 400px;
border-radius: 0;
}
}Security
API Key Management
Never expose API keys client-side:
javascript
// ❌ Wrong - API key exposed
const session = await fetch('https://api.pocketdns.com/api/v1/users', {
headers: {
'Authorization': 'Bearer sk_live_...' // NEVER DO THIS
}
});
// ✅ Correct - API key on server
const session = await fetch('/api/pocketdns/session', {
method: 'POST',
body: JSON.stringify({ userIdentifier })
});Input Validation
Always validate user input:
javascript
const validateUserIdentifier = (identifier) => {
if (!identifier || typeof identifier !== 'string') {
throw new Error('User identifier is required and must be a string');
}
if (identifier.length > 255) {
throw new Error('User identifier too long');
}
if (!/^[a-zA-Z0-9_-]+$/.test(identifier)) {
throw new Error('User identifier contains invalid characters');
}
return identifier.trim();
};Rate Limiting
Implement client-side rate limiting:
javascript
class RateLimiter {
constructor(maxRequests = 100, windowMs = 60000) {
this.requests = [];
this.maxRequests = maxRequests;
this.windowMs = windowMs;
}
canMakeRequest() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.windowMs);
return this.requests.length < this.maxRequests;
}
recordRequest() {
this.requests.push(Date.now());
}
}
const rateLimiter = new RateLimiter(100, 60000); // 100 requests per minuteMonitoring
Request Logging
Log all API requests for debugging:
javascript
const logAPIRequest = (method, url, status, duration, error = null) => {
const logData = {
timestamp: new Date().toISOString(),
method,
url,
status,
duration,
error: error?.message,
userAgent: navigator?.userAgent
};
if (process.env.NODE_ENV === 'development') {
console.log('PocketDNS API Request:', logData);
} else {
// Send to your logging service
sendToLogger(logData);
}
};Health Checks
Monitor API health:
javascript
const checkAPIHealth = async () => {
try {
const start = Date.now();
const response = await fetch('https://api.pocketdns.com/health');
const duration = Date.now() - start;
return {
status: response.ok ? 'healthy' : 'unhealthy',
responseTime: duration,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
status: 'error',
error: error.message,
timestamp: new Date().toISOString()
};
}
};Error Tracking
Track errors for analysis:
javascript
const trackError = (error, context) => {
const errorData = {
message: error.message,
stack: error.stack,
context,
userIdentifier: getCurrentUser()?.id,
url: window.location.href,
timestamp: new Date().toISOString()
};
// Send to error tracking service
if (window.Sentry) {
Sentry.captureException(error, { extra: errorData });
} else {
console.error('PocketDNS Error:', errorData);
}
};Code Organization
Service Layer Pattern
Organize API calls in a service layer:
javascript
// services/pocketdns.js
class PocketDNSService {
constructor(apiKey, baseUrl) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.cache = new Map();
}
async createUserSession(userIdentifier, email) {
const cacheKey = `session_${userIdentifier}`;
const cached = this.cache.get(cacheKey);
if (cached && this.isSessionValid(cached)) {
return cached;
}
const session = await this.apiRequest('POST', '/users', {
user_identifier: userIdentifier,
email
});
this.cache.set(cacheKey, session);
return session;
}
async getUserDomains(userIdentifier) {
return this.apiRequest('GET', `/users/${userIdentifier}/domains`);
}
async apiRequest(method, endpoint, data = null) {
const url = `${this.baseUrl}/api/v1${endpoint}`;
const options = {
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
};
if (data) {
options.body = JSON.stringify(data);
}
const start = Date.now();
try {
const response = await fetch(url, options);
const duration = Date.now() - start;
if (!response.ok) {
throw new Error(`API request failed: ${response.statusText}`);
}
const result = await response.json();
logAPIRequest(method, url, response.status, duration);
return result;
} catch (error) {
const duration = Date.now() - start;
logAPIRequest(method, url, 0, duration, error);
throw error;
}
}
isSessionValid(session) {
const expiryTime = new Date(session.expires_at).getTime();
return Date.now() < expiryTime - 300000; // 5 minutes buffer
}
}
export default PocketDNSService;Environment Configuration
Use environment-specific configurations:
javascript
// config/pocketdns.js
const config = {
development: {
apiUrl: 'https://api.sandbox.pocketdns.com',
embedUrl: 'https://embed.sandbox.pocketdns.com',
apiKey: process.env.POCKETDNS_SANDBOX_API_KEY
},
production: {
apiUrl: 'https://api.pocketdns.com',
embedUrl: 'https://embed.pocketdns.com',
apiKey: process.env.POCKETDNS_API_KEY
}
};
export const getPocketDNSConfig = () => {
const env = process.env.NODE_ENV || 'development';
return config[env];
};Testing Best Practices
- Use Sandbox Environment: Always test in sandbox before production
- Mock API Responses: Create mocks for reliable testing
- Test Error Scenarios: Verify error handling works correctly
- Integration Tests: Test the complete user flow
- Performance Testing: Measure API response times
- Accessibility Testing: Ensure the embed is accessible
