Best Practices
Build robust, secure, and performant integrations with Quub Exchange by following these best practices.
Security
1. Credential Management
β DO:
- Store API keys in environment variables or secrets manager
- Use separate keys for development, staging, and production
- Rotate API keys regularly (every 90 days)
- Implement the principle of least privilege
β DONβT:
- Hardcode credentials in source code
- Commit secrets to version control
- Share API keys between environments
- Use admin keys for routine operations
2. Authentication
// β
Good: Auto-refresh tokens
class AuthManager {
async getToken() {
if (this.isExpiringSoon()) {
await this.refreshToken();
}
return this.accessToken;
}
}
// β Bad: Let tokens expire
const token = await login();
// Use token forever without checking expiry
3. HTTPS Only
// β
Always use HTTPS
const API_BASE = "https://api.quub.exchange";
// β Never use HTTP
const API_BASE = "http://api.quub.exchange"; // WRONG!
Error Handling
Retry Logic with Exponential Backoff
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Don't retry on client errors (4xx)
if (error.response?.status >= 400 && error.response?.status < 500) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000;
await sleep(delay);
}
}
}
Graceful Degradation
async function getMarketPrice(symbol) {
try {
const response = await api.get(`/pricing-refdata/v1/prices/${symbol}`);
return response.data.price;
} catch (error) {
// Log error
logger.error("Failed to fetch price", { symbol, error });
// Return cached price if available
const cached = cache.get(`price:${symbol}`);
if (cached) {
logger.warn("Using cached price", { symbol });
return cached;
}
// Throw if no fallback
throw error;
}
}
Rate Limiting
Implement Client-Side Rate Limiting
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async acquire() {
const now = Date.now();
// Remove old requests outside window
this.requests = this.requests.filter((time) => now - time < this.windowMs);
// Check if limit exceeded
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await sleep(waitTime);
return this.acquire();
}
this.requests.push(now);
}
}
// Usage
const limiter = new RateLimiter(100, 60000); // 100 req/min
async function makeRequest(endpoint) {
await limiter.acquire();
return api.get(endpoint);
}
Handle 429 Responses
async function apiRequest(endpoint) {
try {
return await api.get(endpoint);
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = error.response.headers["retry-after"] || 60;
logger.warn(`Rate limited. Retrying after ${retryAfter}s`);
await sleep(retryAfter * 1000);
return apiRequest(endpoint); // Retry
}
throw error;
}
}
Idempotency
Use Idempotency Keys for Mutations
async function placeOrder(orderParams) {
const idempotencyKey = generateIdempotencyKey(orderParams);
return api.post("/exchange/v1/orders", orderParams, {
headers: {
"X-Idempotency-Key": idempotencyKey,
},
});
}
function generateIdempotencyKey(params) {
// Use stable hash of order params
return crypto
.createHash("sha256")
.update(JSON.stringify(params))
.digest("hex");
}
Performance
Connection Pooling
const axios = require("axios");
const http = require("http");
const https = require("https");
const client = axios.create({
baseURL: "https://api.quub.exchange",
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
timeout: 30000,
});
Caching
const NodeCache = require("node-cache");
const cache = new NodeCache({ stdTTL: 60 }); // 60s TTL
async function getMarketData(symbol) {
const cacheKey = `market:${symbol}`;
// Check cache first
const cached = cache.get(cacheKey);
if (cached) return cached;
// Fetch from API
const data = await api.get(`/market-oracles/v1/prices/${symbol}`);
// Cache for future requests
cache.set(cacheKey, data);
return data;
}
Batch Requests
// β Bad: Multiple requests
async function getMultipleOrders(orderIds) {
const orders = [];
for (const id of orderIds) {
const order = await api.get(`/exchange/v1/orders/${id}`);
orders.push(order);
}
return orders;
}
// β
Good: Single batch request
async function getMultipleOrders(orderIds) {
return api.get("/exchange/v1/orders", {
params: { ids: orderIds.join(",") },
});
}
Monitoring & Observability
Structured Logging
const winston = require("winston");
const logger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "app.log" }),
],
});
// Log with context
logger.info("Order placed", {
orderId: "ord_123",
symbol: "BTC-USD",
side: "buy",
quantity: 0.1,
userId: "usr_456",
});
Metrics Collection
const promClient = require("prom-client");
// Create metrics
const apiRequestDuration = new promClient.Histogram({
name: "api_request_duration_seconds",
help: "API request duration",
labelNames: ["endpoint", "method", "status"],
});
const apiRequestTotal = new promClient.Counter({
name: "api_request_total",
help: "Total API requests",
labelNames: ["endpoint", "method", "status"],
});
// Middleware to track metrics
async function makeRequest(endpoint, options) {
const start = Date.now();
try {
const response = await api(endpoint, options);
const duration = (Date.now() - start) / 1000;
apiRequestDuration.observe(
{
endpoint,
method: options.method,
status: response.status,
},
duration
);
apiRequestTotal.inc({
endpoint,
method: options.method,
status: response.status,
});
return response;
} catch (error) {
const status = error.response?.status || "error";
apiRequestTotal.inc({ endpoint, method: options.method, status });
throw error;
}
}
Health Checks
app.get("/health", async (req, res) => {
const health = {
status: "healthy",
timestamp: new Date().toISOString(),
services: {},
};
try {
// Check API connectivity
await api.get("/health");
health.services.quubApi = "up";
} catch (error) {
health.services.quubApi = "down";
health.status = "unhealthy";
}
const statusCode = health.status === "healthy" ? 200 : 503;
res.status(statusCode).json(health);
});
Testing
Unit Tests
const { QuubClient } = require("./quub-client");
const nock = require("nock");
describe("QuubClient", () => {
let client;
beforeEach(() => {
client = new QuubClient("test-key", "test-secret");
});
it("should place order successfully", async () => {
nock("https://api.quub.exchange").post("/exchange/v1/orders").reply(200, {
orderId: "ord_123",
status: "open",
});
const order = await client.placeOrder({
symbol: "BTC-USD",
side: "buy",
type: "limit",
quantity: 0.1,
price: 45000,
});
expect(order.orderId).toBe("ord_123");
expect(order.status).toBe("open");
});
it("should handle rate limit errors", async () => {
nock("https://api.quub.exchange")
.post("/exchange/v1/orders")
.reply(429, { error: "rate_limit_exceeded" });
await expect(
client.placeOrder({
symbol: "BTC-USD",
side: "buy",
})
).rejects.toThrow("rate_limit_exceeded");
});
});
Integration Tests
describe("Integration: Trading Flow", () => {
let client;
before(async () => {
// Use sandbox environment
client = new QuubClient(
process.env.SANDBOX_API_KEY,
process.env.SANDBOX_API_SECRET,
{ baseURL: "https://sandbox.api.quub.exchange" }
);
await client.login();
});
it("should complete full trading cycle", async () => {
// 1. Check balance
const balance = await client.getBalance("USD");
expect(balance.available).toBeGreaterThan(1000);
// 2. Place order
const order = await client.placeOrder({
symbol: "BTC-USD",
side: "buy",
type: "limit",
quantity: 0.01,
price: 40000,
});
expect(order.status).toBe("open");
// 3. Cancel order
await client.cancelOrder(order.orderId);
// 4. Verify cancellation
const updated = await client.getOrder(order.orderId);
expect(updated.status).toBe("canceled");
});
});
Multi-Tenancy
Always Include Tenant ID
// β
Good: Explicit tenant ID
const config = {
headers: {
Authorization: `Bearer ${token}`,
"X-Tenant-ID": orgId,
},
};
// β Bad: Missing tenant ID
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
Validate Tenant Context
class QuubClient {
constructor(apiKey, apiSecret, orgId) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.orgId = orgId;
}
async makeRequest(endpoint, options = {}) {
// Always validate orgId is set
if (!this.orgId) {
throw new Error("Organization ID not set");
}
return axios({
url: `${this.baseURL}${endpoint}`,
...options,
headers: {
Authorization: `Bearer ${this.accessToken}`,
"X-Tenant-ID": this.orgId,
...options.headers,
},
});
}
}
WebSocket Connections
Reconnection Logic
class QuubWebSocket {
constructor(url, token) {
this.url = url;
this.token = token;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log("Connected");
this.reconnectAttempts = 0;
this.authenticate();
};
this.ws.onclose = () => {
console.log("Disconnected");
this.reconnect();
};
this.ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error("Max reconnection attempts reached");
return;
}
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
console.log(
`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
);
setTimeout(() => this.connect(), delay);
}
authenticate() {
this.ws.send(
JSON.stringify({
type: "auth",
token: this.token,
})
);
}
}
Heartbeat/Ping
class QuubWebSocket {
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: "ping" }));
}
}, 30000); // Every 30 seconds
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
}
}
Deployment
Environment-Specific Configuration
const config = {
development: {
apiUrl: "https://sandbox.api.quub.exchange",
apiKey: process.env.DEV_API_KEY,
logLevel: "debug",
},
staging: {
apiUrl: "https://staging.api.quub.exchange",
apiKey: process.env.STAGING_API_KEY,
logLevel: "info",
},
production: {
apiUrl: "https://api.quub.exchange",
apiKey: process.env.PROD_API_KEY,
logLevel: "warn",
},
};
const env = process.env.NODE_ENV || "development";
module.exports = config[env];
Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async execute(fn) {
if (this.state === "OPEN") {
if (Date.now() < this.nextAttempt) {
throw new Error("Circuit breaker is OPEN");
}
this.state = "HALF_OPEN";
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = "CLOSED";
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = "OPEN";
this.nextAttempt = Date.now() + this.timeout;
}
}
}
Related Resources
- π Authentication Guide
- π Rate Limiting
- π Webhooks
- π Quick Start
Support
Questions about best practices?
- π¬ Community Forum
- π§ Email: support@quub.exchange
- π API Reference