# Node.js Client Implementation Guide This reference provides complete Node.js client implementations for the Asleep API, including webhook servers and production patterns. ## Complete Node.js API Client ```javascript const axios = require('axios'); class AsleepClient { constructor(apiKey, baseURL = 'https://api.asleep.ai') { this.apiKey = apiKey; this.baseURL = baseURL; this.client = axios.create({ baseURL: baseURL, headers: { 'x-api-key': apiKey } }); } async _request(method, path, options = {}) { try { const response = await this.client.request({ method, url: path, ...options }); return response.data; } catch (error) { if (error.response) { const status = error.response.status; const detail = error.response.data?.detail || 'Unknown error'; if (status === 401) { throw new Error('Invalid API key'); } else if (status === 403) { throw new Error(`API access error: ${detail}`); } else if (status === 404) { throw new Error('Resource not found'); } } throw error; } } // User management async createUser(metadata = null) { const data = metadata ? { metadata } : {}; const result = await this._request('POST', '/ai/v1/users', { data }); return result.result.user_id; } async getUser(userId) { const result = await this._request('GET', `/ai/v1/users/${userId}`); return result.result; } async deleteUser(userId) { await this._request('DELETE', `/ai/v1/users/${userId}`); } // Session management async getSession(sessionId, userId, timezone = 'UTC') { const result = await this._request('GET', `/data/v3/sessions/${sessionId}`, { headers: { 'x-user-id': userId, 'timezone': timezone } }); return result.result; } async listSessions(userId, options = {}) { const { dateGte, dateLte, offset = 0, limit = 20, orderBy = 'DESC' } = options; const params = { offset, limit, order_by: orderBy }; if (dateGte) params.date_gte = dateGte; if (dateLte) params.date_lte = dateLte; const result = await this._request('GET', '/data/v1/sessions', { headers: { 'x-user-id': userId }, params }); return result.result; } async deleteSession(sessionId, userId) { await this._request('DELETE', `/ai/v1/sessions/${sessionId}`, { headers: { 'x-user-id': userId } }); } // Statistics async getAverageStats(userId, startDate, endDate, timezone = 'UTC') { const result = await this._request('GET', `/data/v1/users/${userId}/average-stats`, { headers: { 'timezone': timezone }, params: { start_date: startDate, end_date: endDate } }); return result.result; } } // Usage const client = new AsleepClient(process.env.ASLEEP_API_KEY); ``` ## Express Webhook Server ```javascript const express = require('express'); const app = express(); app.use(express.json()); const EXPECTED_API_KEY = process.env.ASLEEP_API_KEY; app.post('/asleep-webhook', async (req, res) => { // Verify authentication const apiKey = req.headers['x-api-key']; const userId = req.headers['x-user-id']; if (apiKey !== EXPECTED_API_KEY) { console.warn('Unauthorized webhook attempt'); return res.status(401).json({ error: 'Unauthorized' }); } const { event, session_id, stat } = req.body; console.log(`Received ${event} event for user ${userId}`); try { switch (event) { case 'INFERENCE_COMPLETE': await handleInferenceComplete(req.body); break; case 'SESSION_COMPLETE': await handleSessionComplete(req.body); break; default: console.warn(`Unknown event type: ${event}`); } res.status(200).json({ status: 'success' }); } catch (error) { console.error('Webhook processing error:', error); res.status(500).json({ error: 'Processing failed' }); } }); async function handleInferenceComplete(event) { const { session_id, user_id, sleep_stages } = event; // Update real-time dashboard await updateLiveDashboard(session_id, sleep_stages); // Store incremental data await db.collection('incremental_data').insertOne(event); console.log(`Processed INFERENCE_COMPLETE for session ${session_id}`); } async function handleSessionComplete(event) { const { session_id, user_id, stat, session } = event; // Store complete report await db.collection('sleep_reports').insertOne({ user_id, session_id, date: session.start_time, statistics: stat, session_data: session, created_at: new Date() }); // Send user notification await sendPushNotification(user_id, { title: 'Sleep Report Ready', body: `Sleep time: ${stat.sleep_time}, Efficiency: ${stat.sleep_efficiency.toFixed(1)}%` }); // Update user statistics await updateUserAggregatedStats(user_id); console.log(`Processed SESSION_COMPLETE for session ${session_id}`); } const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Webhook server listening on port ${PORT}`); }); ``` ## Retry with Exponential Backoff ```javascript async function retryWithExponentialBackoff( func, maxRetries = 3, baseDelay = 1000, maxDelay = 60000 ) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await func(); } catch (error) { if (error.response?.status === 403) { const detail = error.response.data?.detail || ''; if (detail.toLowerCase().includes('rate limit')) { if (attempt < maxRetries - 1) { const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay); console.log(`Rate limited, retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); continue; } } } throw error; } } } // Usage const result = await retryWithExponentialBackoff( () => client.getSession('session123', 'user123') ); ``` ## Basic Usage Examples ### Creating Users ```javascript // Create user with metadata const userId = await client.createUser({ birth_year: 1990, gender: 'male', height: 175.5, weight: 70.0 }); console.log(`Created user: ${userId}`); // Create user without metadata const userId = await client.createUser(); ``` ### Getting Sessions ```javascript // Get sessions for date range const sessions = await client.listSessions('user123', { dateGte: '2024-01-01', dateLte: '2024-01-31', limit: 50, orderBy: 'DESC' }); console.log(`Found ${sessions.sleep_session_list.length} sessions`); sessions.sleep_session_list.forEach(session => { console.log(`Session ${session.session_id}: ${session.session_start_time}`); console.log(` State: ${session.state}, Time in bed: ${session.time_in_bed}s`); }); ``` ### Getting Session Details ```javascript const session = await client.getSession( 'session123', 'user123', 'America/New_York' ); console.log(`Sleep efficiency: ${session.stat.sleep_efficiency.toFixed(1)}%`); console.log(`Total sleep time: ${session.stat.sleep_time}`); console.log(`Sleep stages: ${session.session.sleep_stages}`); console.log(`Sleep cycles: ${session.stat.sleep_cycle.length}`); ``` ### Getting Statistics ```javascript const stats = await client.getAverageStats( 'user123', '2024-01-01', '2024-01-31', 'UTC' ); const avg = stats.average_stats; console.log(`Average sleep time: ${avg.sleep_time}`); console.log(`Average efficiency: ${avg.sleep_efficiency.toFixed(1)}%`); console.log(`Average bedtime: ${avg.start_time}`); console.log(`Average wake time: ${avg.end_time}`); console.log(`Light sleep ratio: ${(avg.light_ratio * 100).toFixed(1)}%`); console.log(`Deep sleep ratio: ${(avg.deep_ratio * 100).toFixed(1)}%`); console.log(`REM sleep ratio: ${(avg.rem_ratio * 100).toFixed(1)}%`); console.log(`Number of sessions: ${stats.slept_sessions.length}`); ``` ## Asynchronous Webhook Processing ```javascript const Queue = require('bull'); const webhookQueue = new Queue('asleep-webhooks', { redis: { host: 'localhost', port: 6379 } }); app.post('/webhook', async (req, res) => { const event = req.body; // Queue for async processing await webhookQueue.add(event); // Respond immediately res.status(200).json({ status: 'queued' }); }); // Process queued webhooks webhookQueue.process(async (job) => { const event = job.data; if (event.event === 'SESSION_COMPLETE') { await handleSessionComplete(event); } else if (event.event === 'INFERENCE_COMPLETE') { await handleInferenceComplete(event); } }); ``` ## Idempotency Pattern ```javascript async function handleSessionComplete(event) { const sessionId = event.session_id; // Check if already processed const existing = await db.collection('processed_webhooks').findOne({ session_id: sessionId, event: 'SESSION_COMPLETE' }); if (existing) { console.log(`Session ${sessionId} already processed, skipping`); return; } // Process event await saveSleepReport(event); // Mark as processed await db.collection('processed_webhooks').insertOne({ session_id: sessionId, event: 'SESSION_COMPLETE', processed_at: new Date() }); } ``` ## Comprehensive Error Handling ```javascript class AsleepAPIError extends Error { constructor(message, statusCode, detail) { super(message); this.name = 'AsleepAPIError'; this.statusCode = statusCode; this.detail = detail; } } class RateLimitError extends AsleepAPIError { constructor(detail) { super('Rate limit exceeded', 403, detail); this.name = 'RateLimitError'; } } class ResourceNotFoundError extends AsleepAPIError { constructor(detail) { super('Resource not found', 404, detail); this.name = 'ResourceNotFoundError'; } } async function safeApiRequest(requestFunc) { try { return await requestFunc(); } catch (error) { if (error.response) { const status = error.response.status; const detail = error.response.data?.detail || 'Unknown error'; if (status === 401) { throw new AsleepAPIError('Authentication failed', 401, detail); } else if (status === 403) { if (detail.toLowerCase().includes('rate limit')) { throw new RateLimitError(detail); } else { throw new AsleepAPIError('Access forbidden', 403, detail); } } else if (status === 404) { throw new ResourceNotFoundError(detail); } else { throw new AsleepAPIError(`API error (${status})`, status, detail); } } throw error; } } // Usage try { const user = await safeApiRequest(() => client.getUser('user123')); } catch (error) { if (error instanceof ResourceNotFoundError) { console.log('User not found, creating new user...'); const userId = await client.createUser(); } else if (error instanceof RateLimitError) { console.log('Rate limited, try again later'); } else if (error instanceof AsleepAPIError) { console.error(`API error: ${error.message}`); } } ``` ## Production Configuration ```javascript // config.js require('dotenv').config(); class Config { static get ASLEEP_API_KEY() { return process.env.ASLEEP_API_KEY; } static get ASLEEP_BASE_URL() { return process.env.ASLEEP_BASE_URL || 'https://api.asleep.ai'; } static get DATABASE_URL() { return process.env.DATABASE_URL; } static get REDIS_URL() { return process.env.REDIS_URL; } static get WEBHOOK_SECRET() { return process.env.WEBHOOK_SECRET; } static get ENABLE_CACHING() { return process.env.ENABLE_CACHING !== 'false'; } static validate() { if (!this.ASLEEP_API_KEY) { throw new Error('ASLEEP_API_KEY environment variable required'); } if (!this.DATABASE_URL) { throw new Error('DATABASE_URL environment variable required'); } } } module.exports = Config; ```