JavaScript/TypeScript SDK
Complete guide to using ScoutQuest in JavaScript and TypeScript applications.
Installation
npm install scoutquest-js
yarn add scoutquest-js
pnpm add scoutquest-js
bun add scoutquest-js
Quick Start
Get started with ScoutQuest in just a few lines of code:
import { ScoutQuestClient } from 'scoutquest-js';
// Create client
const client = new ScoutQuestClient('http://localhost:8080');
// Register your service
const instance = await client.registerService('my-service', 'localhost', 3000, {
tags: ['api', 'v1'],
metadata: {
version: '1.0.0',
environment: 'production'
}
});
console.log(`Service registered with ID: ${instance.id}`);
// Discover other services
const userService = await client.discoverService('user-service');
console.log(`User service found at: ${userService.host}:${userService.port}`);
// Make HTTP calls through ScoutQuest
const users = await client.get('user-service', '/api/users');
console.log('Users:', users.data);
Client Configuration
Basic Configuration
import { ScoutQuestClient } from 'scoutquest-js';
const client = new ScoutQuestClient('http://localhost:8080', {
timeout: 5000, // Request timeout in milliseconds
retries: 3, // Number of retry attempts
retryDelay: 1000, // Delay between retries in milliseconds
headers: { // Default headers for all requests
'User-Agent': 'MyApp/1.0.0'
}
});
Advanced Configuration
const client = new ScoutQuestClient('http://localhost:8080', {
// HTTP client configuration
timeout: 10000,
retries: 5,
retryDelay: 2000,
// Custom retry logic
retryCondition: (error) => {
return error.response?.status >= 500 || error.code === 'ECONNRESET';
},
// Request/Response interceptors
requestInterceptor: (config) => {
config.headers['X-Request-ID'] = generateRequestId();
return config;
},
responseInterceptor: (response) => {
console.log(`Request to ${response.config.url} took ${response.duration}ms`);
return response;
},
// Error handling
errorHandler: (error) => {
console.error('ScoutQuest error:', error);
// Custom error reporting logic
}
});
Service Registration
Basic Registration
// Simple registration
const instance = await client.registerService('api-service', 'localhost', 3000);
// With tags and metadata
const instance = await client.registerService('api-service', 'localhost', 3000, {
tags: ['api', 'v1', 'production'],
metadata: {
version: '1.2.3',
environment: 'production',
region: 'us-east-1'
}
});
Health Check Configuration
const instance = await client.registerService('api-service', 'localhost', 3000, {
tags: ['api', 'v1'],
healthCheck: {
path: '/health', // Health check endpoint
interval: 30, // Check interval in seconds
timeout: 5, // Timeout for health checks in seconds
retries: 3, // Number of failed checks before marking unhealthy
successThreshold: 2 // Number of successful checks to mark healthy
}
});
Service Deregistration
// Deregister specific instance
await client.deregisterService(instance.id);
// Deregister all instances of a service
await client.deregisterAllInstances('api-service');
// Graceful shutdown with cleanup
process.on('SIGTERM', async () => {
console.log('Gracefully shutting down...');
await client.deregisterService(instance.id);
process.exit(0);
});
Service Discovery
Basic Discovery
// Discover a service (returns single instance)
const instance = await client.discoverService('user-service');
console.log(`Service at: ${instance.host}:${instance.port}`);
// List all services
const services = await client.listServices();
services.forEach(service => {
console.log(`Service: ${service.name} - ${service.instanceCount} instances`);
});
// List instances of a specific service
const instances = await client.listServiceInstances('user-service');
instances.forEach(instance => {
console.log(`Instance: ${instance.id} at ${instance.host}:${instance.port}`);
});
Advanced Discovery with Filtering
// Discover with tag filtering
const instance = await client.discoverService('user-service', {
tags: ['v1', 'production'], // Service must have these tags
excludeTags: ['deprecated'], // Service must not have these tags
metadata: { // Service metadata filters
region: 'us-east-1',
version: '^1.2.0' // Semver matching
}
});
// Custom discovery logic
const instance = await client.discoverService('user-service', {
filter: (instances) => {
// Custom filtering logic
return instances.filter(inst =>
inst.metadata.load < 0.8 &&
inst.metadata.responseTime < 100
);
}
});
HTTP Calls Through Service Discovery
Basic HTTP Methods
// GET request
const users = await client.get('user-service', '/api/users');
console.log(users.data);
// GET with query parameters
const user = await client.get('user-service', '/api/users/:id', {
params: { id: '123' },
query: { include: 'profile' }
});
// POST request
const newUser = await client.post('user-service', '/api/users', {
data: {
name: 'John Doe',
email: 'john@example.com'
}
});
// PUT request
const updatedUser = await client.put('user-service', '/api/users/:id', {
params: { id: '123' },
data: { name: 'John Smith' }
});
// DELETE request
await client.delete('user-service', '/api/users/:id', {
params: { id: '123' }
});
Advanced HTTP Configuration
// Request with custom headers and timeout
const response = await client.get('user-service', '/api/users', {
headers: {
'Authorization': 'Bearer ' + token,
'X-Client-Version': '1.0.0'
},
timeout: 30000,
retries: 5,
retryDelay: 1000
});
// Request with custom service selection
const response = await client.post('user-service', '/api/users', {
data: userData,
serviceOptions: {
tags: ['v1'], // Use only v1 instances
preferLocal: true, // Prefer instances on same host
strategy: 'round-robin' // Load balancing strategy
}
});
// Raw HTTP request with full control
const response = await client.request('user-service', {
method: 'POST',
path: '/api/users',
data: userData,
headers: { 'Content-Type': 'application/json' },
timeout: 15000
});
Error Handling
import {
ScoutQuestClient,
ServiceNotFoundError,
ServiceUnavailableError,
ScoutQuestError
} from 'scoutquest-js';
try {
const instance = await client.discoverService('user-service');
const users = await client.get('user-service', '/api/users');
} catch (error) {
if (error instanceof ServiceNotFoundError) {
console.error('Service not found:', error.serviceName);
// Handle service not found
} else if (error instanceof ServiceUnavailableError) {
console.error('Service unavailable:', error.message);
// Handle service down/unhealthy
} else if (error instanceof ScoutQuestError) {
console.error('ScoutQuest error:', error.message);
// Handle other ScoutQuest-specific errors
} else {
console.error('Unexpected error:', error);
// Handle other errors
}
}
Custom Error Handler
const client = new ScoutQuestClient('http://localhost:8080', {
errorHandler: (error, context) => {
// Log error details
console.error(`Error in ${context.operation}:`, {
error: error.message,
service: context.serviceName,
endpoint: context.endpoint,
timestamp: new Date().toISOString()
});
// Send to monitoring system
monitoring.recordError(error, context);
// Custom retry logic
if (error.code === 'ECONNRESET' && context.retryCount < 3) {
return { shouldRetry: true, delay: 2000 };
}
return { shouldRetry: false };
}
});
TypeScript Support
ScoutQuest provides full TypeScript support with comprehensive type definitions:
Type Definitions
import {
ScoutQuestClient,
ServiceInstance,
ServiceRegistration,
ServiceDiscoveryOptions,
HttpRequestOptions
} from 'scoutquest-js';
// Custom service interface
interface UserService {
id: string;
name: string;
email: string;
}
// Typed HTTP calls
const users = await client.get<UserService[]>('user-service', '/api/users');
const user = await client.post<UserService>('user-service', '/api/users', {
data: { name: 'John', email: 'john@example.com' }
});
// Typed service registration
const registration: ServiceRegistration = {
tags: ['api', 'v1'],
metadata: {
version: '1.0.0',
capabilities: ['users', 'auth']
},
healthCheck: {
path: '/health',
interval: 30
}
};
const instance = await client.registerService('api-service', 'localhost', 3000, registration);
Custom Type Guards
import { ServiceInstance } from 'scoutquest-js';
// Type guard for service capabilities
function hasCapability(instance: ServiceInstance, capability: string): boolean {
return instance.metadata?.capabilities?.includes(capability) ?? false;
}
// Usage
const instance = await client.discoverService('user-service');
if (hasCapability(instance, 'auth')) {
const token = await client.post<{token: string}>('user-service', '/auth/login', {
data: { username, password }
});
}
Framework Integration
Express.js Integration
import express from 'express';
import { ScoutQuestClient } from 'scoutquest-js';
const app = express();
const client = new ScoutQuestClient('http://localhost:8080');
// Middleware for service discovery
app.use((req, res, next) => {
req.scoutquest = client;
next();
});
// Register service on startup
let serviceInstance;
app.listen(3000, async () => {
serviceInstance = await client.registerService('api-service', 'localhost', 3000, {
tags: ['api', 'express'],
healthCheck: { path: '/health' }
});
console.log('Server started and registered with ScoutQuest');
});
// Route that calls another service
app.get('/users', async (req, res) => {
try {
const users = await req.scoutquest.get('user-service', '/api/users');
res.json(users.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Graceful shutdown
process.on('SIGTERM', async () => {
if (serviceInstance) {
await client.deregisterService(serviceInstance.id);
}
process.exit(0);
});
Next.js Integration
// lib/scoutquest.ts
import { ScoutQuestClient } from 'scoutquest-js';
let client: ScoutQuestClient;
if (typeof window === 'undefined') {
// Server-side only
client = new ScoutQuestClient(process.env.SCOUTQUEST_URL!);
}
export { client };
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { client } from '../../lib/scoutquest';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const users = await client.get('user-service', '/api/users');
res.status(200).json(users.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
}
Best Practices
Service Registration
- Always register your service on startup
- Include meaningful tags for service discovery
- Implement proper health check endpoints
- Deregister on graceful shutdown
Error Handling
- Always wrap service calls in try-catch blocks
- Implement retry logic for transient failures
- Use circuit breaker patterns for downstream services
- Log errors with sufficient context for debugging
Performance
- Reuse ScoutQuestClient instances
- Configure appropriate timeouts and retries
- Use connection pooling for high-throughput scenarios
- Cache service discovery results when appropriate
Examples
Check out these complete examples to see ScoutQuest in action: