Overview
Following these best practices will help you get the most out of SnakeQuery while building reliable, performant applications. These guidelines are based on real-world usage patterns and common pitfalls.
Quick Start Checklist: Use environment variables, always implement error handling, define response schemas, and write specific queries.
๐ Security Best Practices
API Key Management
Never expose API keys in client-side code or commit them to version control.
โ
Correct Approaches
Environment Variables (Recommended)
# .env file
SNAKE_QUERY_API_KEY=your_api_key_here
// Node.js
const client = new SnakeQuery(process.env.SNAKE_QUERY_API_KEY);
# Python
import os
client = SnakeQuery(api_key=os.environ.get('SNAKE_QUERY_API_KEY'))
Backend Proxy for Frontend Applications
// Backend endpoint
app.post('/api/query', async (req, res) => {
try {
const response = await fetch('https://app.snakequery.com/api/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SNAKE_QUERY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(req.body)
});
const result = await response.json();
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Frontend usage
const result = await fetch('/api/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'Find products under $100',
data: products
})
});
โ Common Mistakes
// DON'T: Hardcode API keys
const client = new SnakeQuery('sk-1234567890abcdef...');
// DON'T: Expose in frontend JavaScript
const apiKey = 'sk-1234567890abcdef...';
fetch('https://app.snakequery.com/api/query', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
// DON'T: Commit to version control
git add .env // Contains API keys
Always validate and sanitize user-provided data:
function validateQuery(userInput) {
// Validate query length
if (!userInput || userInput.length > 1000) {
throw new Error('Invalid query length');
}
// Sanitize potentially dangerous content
const sanitized = userInput.replace(/[<>\"']/g, '');
return sanitized;
}
// Usage
const userQuery = validateQuery(req.body.query);
const result = await client.query({
query: userQuery,
data: safeData
});
๐ Schema Design Best Practices
Always Use Schemas in Production
Schemas provide type safety, validation, and consistent response structures essential for production applications.
โ
Production-Ready Schema
const productSchema = {
type: 'array',
items: {
type: 'object',
properties: {
productId: { type: 'string' },
name: { type: 'string', minLength: 1 },
price: { type: 'number', minimum: 0, maximum: 999999 },
category: { type: 'string' },
inStock: { type: 'boolean' }
},
required: ['productId', 'name', 'price']
}
};
const result = await client.query({
query: 'Find available products under $500',
data: products,
responseSchema: productSchema // Ensures consistent structure
});
// DON'T: No schema in production
const result = await client.query({
query: 'Find products',
data: products
// Missing responseSchema - unpredictable response structure
});
Schema Versioning
As your application evolves, version your schemas:
const productSchemaV1 = {
type: 'object',
properties: {
name: { type: 'string' },
price: { type: 'number' }
}
};
const productSchemaV2 = {
type: 'object',
properties: {
name: { type: 'string' },
price: { type: 'number' },
currency: { type: 'string', enum: ['USD', 'EUR', 'GBP'] },
availability: {
type: 'object',
properties: {
inStock: { type: 'boolean' },
quantity: { type: 'integer', minimum: 0 }
}
}
},
required: ['name', 'price', 'currency']
};
// Use version-specific schemas
const currentSchema = productSchemaV2;
Query Specificity
Write specific, focused queries for better performance and accuracy:
โ
Specific Queries
// GOOD: Specific and focused
const result = await client.query({
query: 'Find the 5 most expensive electronics products under $1000, show name, price, and brand',
data: products,
responseSchema: topProductsSchema
});
// GOOD: Clear constraints
const result = await client.query({
query: 'Show products added in the last 30 days with ratings above 4 stars',
fetchUrl: 'https://api.store.com/recent-products',
responseSchema: recentProductsSchema
});
โ Overly Broad Queries
// BAD: Too vague and broad
const result = await client.query({
query: 'Show me everything about all products',
data: products
});
// BAD: No constraints
const result = await client.query({
query: 'Find products',
fetchUrl: 'https://api.store.com/products'
});
Data Source Selection
Choose the appropriate data source for your query:
Direct Data (Best for Small Datasets)
// Use for arrays with < 1000 items
const smallProductList = products.slice(0, 100);
const result = await client.query({
query: 'Find products by category',
data: smallProductList,
responseSchema: schema
});
URL Endpoints (Best for Large Datasets)
// Use for large datasets or when data is already available via API
const result = await client.query({
query: 'Analyze sales trends over the last year',
fetchUrl: 'https://api.company.com/sales/yearly-data',
responseSchema: analyticsSchema
});
Result Caching
Implement caching for frequently used queries:
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function cachedQuery(queryOptions) {
const cacheKey = JSON.stringify(queryOptions);
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.result;
}
const result = await client.query(queryOptions);
cache.set(cacheKey, {
result,
timestamp: Date.now()
});
return result;
}
// Usage
const result = await cachedQuery({
query: 'Get product categories with counts',
fetchUrl: 'https://api.store.com/products'
});
๐จ Error Handling
Comprehensive Error Handling
Always implement robust error handling:
async function robustQuery(queryOptions, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await client.query(queryOptions);
return result;
} catch (error) {
console.error(`Query attempt ${attempt} failed:`, error.message);
// Handle specific error types
if (error.status === 401) {
throw new Error('Invalid API key - check your credentials');
}
if (error.status === 402) {
throw new Error('Insufficient credits - please upgrade your plan');
}
if (error.status === 429) {
// Rate limit exceeded - wait and retry
if (attempt < maxRetries) {
console.log(`Rate limited, waiting ${attempt * 1000}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, attempt * 1000));
continue;
}
}
if (error.status >= 500 && attempt < maxRetries) {
// Server error - retry with exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Server error, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Re-throw if not retryable or max retries reached
throw error;
}
}
}
// Usage with error handling
try {
const result = await robustQuery({
query: 'Analyze customer data',
fetchUrl: 'https://api.company.com/customers'
});
console.log('Success:', result.response);
} catch (error) {
console.error('Query failed permanently:', error.message);
// Handle gracefully - show user-friendly message, use default data, etc.
}
Error Response Handling
Handle different error scenarios appropriately:
function handleQueryError(error) {
switch (error.status) {
case 400:
return {
userMessage: 'Please refine your search query',
action: 'retry_with_different_query'
};
case 401:
return {
userMessage: 'Authentication failed',
action: 'check_api_key'
};
case 402:
return {
userMessage: 'Usage limit reached',
action: 'upgrade_plan'
};
case 429:
return {
userMessage: 'Too many requests, please wait',
action: 'retry_later'
};
case 504:
return {
userMessage: 'Query too complex, please simplify',
action: 'simplify_query'
};
default:
return {
userMessage: 'Service temporarily unavailable',
action: 'try_again_later'
};
}
}
Health Checks
Monitor SnakeQuery API health:
async function healthCheck() {
try {
const result = await client.query({
query: 'Count items',
data: [{ test: 'item' }],
responseSchema: { type: 'number' }
});
return {
status: 'healthy',
responseTime: Date.now() - start,
tokensUsed: result.usageCount.totalTokens
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString()
};
}
}
// Run health checks periodically
setInterval(async () => {
const health = await healthCheck();
console.log('SnakeQuery Health:', health);
}, 5 * 60 * 1000); // Every 5 minutes
Request Deduplication
Avoid duplicate queries:
const activeRequests = new Map();
async function deduplicatedQuery(queryOptions) {
const requestKey = JSON.stringify(queryOptions);
// Return existing promise if request is already in progress
if (activeRequests.has(requestKey)) {
return activeRequests.get(requestKey);
}
// Create new request
const requestPromise = client.query(queryOptions)
.finally(() => {
// Clean up after completion
activeRequests.delete(requestKey);
});
activeRequests.set(requestKey, requestPromise);
return requestPromise;
}
๐ Monitoring and Analytics
Usage Tracking
Track your SnakeQuery usage patterns:
class SnakeQueryMonitor {
constructor(client) {
this.client = client;
this.metrics = {
totalQueries: 0,
totalTokens: 0,
errorCount: 0,
responseTimeSum: 0,
queryPatterns: {}
};
}
async query(queryOptions) {
const startTime = Date.now();
const queryPattern = this.extractPattern(queryOptions.query);
try {
const result = await this.client.query(queryOptions);
// Track success metrics
this.metrics.totalQueries++;
this.metrics.totalTokens += result.usageCount.totalTokens;
this.metrics.responseTimeSum += Date.now() - startTime;
this.metrics.queryPatterns[queryPattern] =
(this.metrics.queryPatterns[queryPattern] || 0) + 1;
return result;
} catch (error) {
this.metrics.errorCount++;
throw error;
}
}
extractPattern(query) {
// Extract query pattern for analytics
return query.toLowerCase()
.replace(/\d+/g, 'N')
.replace(/"[^"]*"/g, '"STRING"')
.trim();
}
getAnalytics() {
return {
...this.metrics,
averageResponseTime: this.metrics.responseTimeSum / this.metrics.totalQueries,
errorRate: this.metrics.errorCount / this.metrics.totalQueries,
averageTokensPerQuery: this.metrics.totalTokens / this.metrics.totalQueries
};
}
}
// Usage
const monitoredClient = new SnakeQueryMonitor(client);
Cost Optimization
Monitor and optimize API costs:
function calculateCost(usageCount, pricePerToken = 0.001) {
return {
inputCost: usageCount.promptTokens * pricePerToken,
outputCost: usageCount.completionTokens * (pricePerToken * 2),
totalCost: usageCount.totalTokens * pricePerToken
};
}
async function costAwareQuery(queryOptions, maxCostCents = 10) {
const result = await client.query(queryOptions);
const cost = calculateCost(result.usageCount);
if (cost.totalCost * 100 > maxCostCents) {
console.warn(`High cost query: $${cost.totalCost.toFixed(4)}`);
}
return {
...result,
estimatedCost: cost
};
}
๐งช Testing Best Practices
Unit Testing
Test your SnakeQuery integration:
// Mock for testing
const mockSnakeQuery = {
query: jest.fn()
};
describe('Product Search', () => {
beforeEach(() => {
mockSnakeQuery.query.mockReset();
});
test('should handle successful product search', async () => {
const mockResponse = {
response: [{ name: 'iPhone', price: 999 }],
usageCount: { totalTokens: 100 }
};
mockSnakeQuery.query.mockResolvedValue(mockResponse);
const result = await searchProducts('phones under $1000', mockSnakeQuery);
expect(result).toEqual(mockResponse.response);
expect(mockSnakeQuery.query).toHaveBeenCalledWith({
query: 'phones under $1000',
data: expect.any(Array),
responseSchema: expect.any(Object)
});
});
test('should handle API errors gracefully', async () => {
mockSnakeQuery.query.mockRejectedValue(new Error('API Error'));
const result = await searchProducts('test query', mockSnakeQuery);
expect(result).toEqual([]); // Should return empty array on error
});
});
Integration Testing
Test with real API in staging:
describe('SnakeQuery Integration', () => {
const testClient = new SnakeQuery(process.env.SNAKE_QUERY_TEST_API_KEY);
test('should query test data successfully', async () => {
const testData = [
{ name: 'Test Product', price: 100, category: 'test' }
];
const result = await testClient.query({
query: 'Find products under $200',
data: testData,
responseSchema: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
price: { type: 'number' }
}
}
}
});
expect(result.response).toHaveLength(1);
expect(result.usageCount.totalTokens).toBeGreaterThan(0);
});
});
๐ Development Checklist
Before deploying to production, ensure:
Security โ
Error Handling โ
Monitoring โ
Testing โ
Common Pitfalls to Avoid
1. Schema Mismatches
// DON'T: Expect different structure than schema
const schema = {
type: 'array',
items: { type: 'string' }
};
// Query expects objects but schema defines strings
const result = await client.query({
query: 'Show product names and prices', // Returns objects
data: products,
responseSchema: schema // Expects strings
});
2. Overly Complex Queries
// DON'T: Combine too many operations
const result = await client.query({
query: 'Find products, group by category, calculate averages, sort by ratings, show trends, and predict future sales',
data: products
});
// DO: Break into separate queries
const categorized = await client.query({
query: 'Group products by category with averages',
data: products,
responseSchema: categorySchema
});
const trends = await client.query({
query: 'Analyze sales trends',
data: salesData,
responseSchema: trendSchema
});
Next Steps