Node.js Examples

Complete Node.js integration examples for LoomAPI.

Node.js Integration Examples

Comprehensive examples for integrating LoomAPI with Node.js applications, including Express, Fastify, and serverless environments.

Installation

npm install @loomapi/node-sdk
# or
yarn add @loomapi/node-sdk
# or
pnpm add @loomapi/node-sdk

Basic Usage

Simple Verification

const { LoomAPI } = require('@loomapi/node-sdk')

// Initialize client
const loom = new LoomAPI({
  apiKey: process.env.LOOM_API_KEY,
  // Optional configuration
  baseURL: 'https://api.loomapi.com',
  timeout: 30000,
  maxRetries: 3
})

// Basic verification
async function verifyDocument(documentData) {
  try {
    const result = await loom.verify({
      documentType: 'passport',
      documentData: documentData, // Base64 encoded
      verificationType: 'age_only'
    })

    if (result.status === 'verified') {
      console.log(`✅ Verified age: ${result.verified_age}`)
      console.log(`📊 Confidence: ${(result.confidence_score * 100).toFixed(1)}%`)
      return { success: true, age: result.verified_age }
    } else {
      console.log('❌ Verification failed')
      return { success: false, error: result.error?.message }
    }
  } catch (error) {
    console.error('🚨 Error:', error.message)
    return { success: false, error: error.message }
  }
}

Express.js Integration

Age Verification Middleware

const express = require('express')
const { LoomAPI } = require('@loomapi/node-sdk')
const multer = require('multer')
const fs = require('fs').promises

const app = express()
const loom = new LoomAPI({ apiKey: process.env.LOOM_API_KEY })

// Configure multer for file uploads
const upload = multer({
  limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/') || file.mimetype === 'application/pdf') {
      cb(null, true)
    } else {
      cb(new Error('Invalid file type'))
    }
  }
})

// Age verification middleware
const requireAgeVerification = (minAge = 18) => {
  return async (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' })
    }

    if (req.user.age_verified && req.user.verified_age >= minAge) {
      return next() // Already verified
    }

    res.status(403).json({
      error: 'Age verification required',
      redirectTo: '/verify-age'
    })
  }
}

// File upload and verification endpoint
app.post('/api/verify-age', upload.single('document'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No document uploaded' })
    }

    // Convert file to base64
    const fileBuffer = await fs.readFile(req.file.path)
    const base64Data = fileBuffer.toString('base64')

    // Determine document type
    const documentType = req.body.document_type || 'passport'

    // Verify with LoomAPI
    const result = await loom.verify({
      documentType,
      documentData: base64Data,
      metadata: {
        user_id: req.user?.id,
        ip_address: req.ip,
        user_agent: req.get('User-Agent')
      }
    })

    // Clean up uploaded file
    await fs.unlink(req.file.path)

    if (result.status === 'verified') {
      // Update user record
      if (req.user) {
        req.user.verified_age = result.verified_age
        req.user.age_verified = true
        req.user.verification_date = new Date()
        await req.user.save()
      }

      res.json({
        success: true,
        age: result.verified_age,
        confidence: result.confidence_score,
        request_id: result.request_id
      })
    } else {
      res.status(400).json({
        success: false,
        error: 'Verification failed',
        details: result.error?.message
      })
    }

  } catch (error) {
    console.error('Verification error:', error)

    // Clean up file if it exists
    if (req.file?.path) {
      try {
        await fs.unlink(req.file.path)
      } catch (cleanupError) {
        console.error('Cleanup error:', cleanupError)
      }
    }

    res.status(500).json({
      success: false,
      error: 'Verification service temporarily unavailable'
    })
  }
})

// Protected route example
app.get('/adult-content', requireAgeVerification(18), (req, res) => {
  res.json({ message: 'Welcome to adult content!' })
})

app.listen(3000, () => {
  console.log('Server running on port 3000')
})

User Registration Flow

const express = require('express')
const bcrypt = require('bcrypt')
const { LoomAPI } = require('@loomapi/node-sdk')

const app = express()
const loom = new LoomAPI({ apiKey: process.env.LOOM_API_KEY })

app.use(express.json())

class UserRegistrationService {
  constructor() {
    this.loom = loom
  }

  async registerUser(userData, documentData) {
    const { email, password, firstName, lastName } = userData

    // 1. Create user account (without age verification)
    const hashedPassword = await bcrypt.hash(password, 12)
    const user = await User.create({
      email,
      password: hashedPassword,
      firstName,
      lastName,
      age_verified: false
    })

    // 2. Verify age in background
    try {
      const verificationResult = await this.loom.verify({
        documentType: 'passport',
        documentData: documentData,
        metadata: { user_id: user.id, action: 'registration' }
      })

      if (verificationResult.status === 'verified') {
        // 3. Update user with verified age
        user.verified_age = verificationResult.verified_age
        user.age_verified = true
        user.verification_completed_at = new Date()
        await user.save()

        return {
          success: true,
          user: {
            id: user.id,
            email: user.email,
            age: user.verified_age,
            age_verified: true
          },
          message: 'Registration successful with age verification'
        }
      } else {
        // Verification failed - mark for manual review
        user.verification_status = 'failed'
        user.verification_error = verificationResult.error?.message
        await user.save()

        return {
          success: false,
          error: 'Age verification failed. Please contact support.',
          user_id: user.id
        }
      }
    } catch (error) {
      // Verification service unavailable - allow registration but flag for later verification
      console.error('Age verification service error:', error)

      user.verification_status = 'pending'
      await user.save()

      return {
        success: true,
        user: {
          id: user.id,
          email: user.email,
          age_verified: false
        },
        message: 'Registration successful. Age verification will be completed shortly.',
        requires_manual_verification: true
      }
    }
  }
}

const registrationService = new UserRegistrationService()

app.post('/api/register', async (req, res) => {
  try {
    const { email, password, firstName, lastName, documentData } = req.body

    // Basic validation
    if (!email || !password || !documentData) {
      return res.status(400).json({ error: 'Missing required fields' })
    }

    const result = await registrationService.registerUser(
      { email, password, firstName, lastName },
      documentData
    )

    if (result.success) {
      res.status(201).json(result)
    } else {
      res.status(400).json({ error: result.error })
    }

  } catch (error) {
    console.error('Registration error:', error)
    res.status(500).json({ error: 'Registration failed' })
  }
})

Webhook Handling

Express Webhook Endpoint

const express = require('express')
const crypto = require('crypto')
const { verifyWebhookSignature } = require('@loomapi/node-sdk')

const app = express()
app.use(express.json())

const WEBHOOK_SECRET = process.env.LOOM_WEBHOOK_SECRET

// Webhook verification middleware
function verifyLoomWebhook(req, res, next) {
  const signature = req.headers['x-loom-signature']

  if (!signature) {
    return res.status(401).json({ error: 'Missing signature' })
  }

  try {
    const isValid = verifyWebhookSignature(
      req.body,
      signature,
      WEBHOOK_SECRET
    )

    if (!isValid) {
      return res.status(401).json({ error: 'Invalid signature' })
    }

    next()
  } catch (error) {
    console.error('Webhook verification error:', error)
    res.status(401).json({ error: 'Signature verification failed' })
  }
}

// Webhook endpoint
app.post('/webhooks/loom', verifyLoomWebhook, async (req, res) => {
  const { event, request_id, verification, metadata } = req.body

  console.log(`📥 Received webhook: ${event} for request ${request_id}`)

  try {
    switch (event) {
      case 'verification.completed':
        await handleVerificationCompleted(request_id, verification, metadata)
        break

      case 'verification.failed':
        await handleVerificationFailed(request_id, verification, metadata)
        break

      case 'verification.expired':
        await handleVerificationExpired(request_id, metadata)
        break

      default:
        console.log(`⚠️ Unhandled event: ${event}`)
    }

    res.status(200).json({ received: true })

  } catch (error) {
    console.error('❌ Webhook processing error:', error)
    res.status(500).json({ error: 'Processing failed' })
  }
})

async function handleVerificationCompleted(requestId, verification, metadata) {
  console.log(`✅ Verification completed: ${requestId}`)

  // Find user by request ID or metadata
  const user = await User.findOne({
    $or: [
      { verification_request_id: requestId },
      { id: metadata?.user_id }
    ]
  })

  if (!user) {
    console.error(`User not found for request ${requestId}`)
    return
  }

  // Update user record
  user.verified_age = verification.verified_age
  user.age_verified = true
  user.verification_completed_at = new Date()
  user.verification_confidence = verification.confidence_score

  await user.save()

  // Send confirmation email
  await sendVerificationSuccessEmail(user.email, {
    age: verification.verified_age,
    confidence: verification.confidence_score
  })

  // Log for analytics
  await logVerificationEvent('completed', {
    user_id: user.id,
    request_id: requestId,
    age: verification.verified_age,
    confidence: verification.confidence_score
  })
}

async function handleVerificationFailed(requestId, verification, metadata) {
  console.log(`❌ Verification failed: ${requestId}`)

  const user = await User.findOne({
    $or: [
      { verification_request_id: requestId },
      { id: metadata?.user_id }
    ]
  })

  if (user) {
    user.verification_failed_at = new Date()
    user.verification_error = verification.error?.message
    user.verification_attempts = (user.verification_attempts || 0) + 1

    await user.save()

    // Send failure notification
    await sendVerificationFailedEmail(user.email, verification.error?.message)
  }

  // Log failure for monitoring
  await logVerificationEvent('failed', {
    request_id: requestId,
    user_id: metadata?.user_id,
    error: verification.error?.message
  })
}

async function handleVerificationExpired(requestId, metadata) {
  console.log(`⏰ Verification expired: ${requestId}`)

  const user = await User.findOne({
    $or: [
      { verification_request_id: requestId },
      { id: metadata?.user_id }
    ]
  })

  if (user && !user.age_verified) {
    // Mark as expired, user can try again
    user.verification_status = 'expired'
    await user.save()

    await sendVerificationExpiredEmail(user.email)
  }
}

Error Handling & Retries

Comprehensive Error Handling

class LoomService {
  constructor(apiKey) {
    this.loom = new LoomAPI({
      apiKey,
      maxRetries: 3,
      retryDelay: 1000
    })
  }

  async verifyWithRetry(documentData, options = {}) {
    const {
      maxRetries = 3,
      documentType = 'passport',
      metadata = {}
    } = options

    let lastError

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        console.log(`🔄 Verification attempt ${attempt}/${maxRetries}`)

        const result = await this.loom.verify({
          documentType,
          documentData,
          metadata: {
            ...metadata,
            attempt,
            max_retries: maxRetries
          }
        })

        if (result.status === 'verified') {
          console.log(`✅ Verification successful on attempt ${attempt}`)
          return result
        }

        // Handle specific verification failures
        if (result.error?.code === 'DOCUMENT_QUALITY_LOW') {
          throw new Error('Document quality too low for verification')
        }

        if (result.error?.code === 'VERIFICATION_FAILED') {
          throw new Error('Document verification failed')
        }

        // For other errors, continue to retry
        lastError = new Error(result.error?.message || 'Verification failed')

      } catch (error) {
        lastError = error

        // Check if error is retryable
        if (this.isRetryableError(error)) {
          if (attempt < maxRetries) {
            const delay = this.calculateDelay(attempt, error)
            console.log(`⏳ Retrying in ${delay}ms...`)
            await this.delay(delay)
            continue
          }
        } else {
          // Non-retryable error (like invalid document)
          break
        }
      }
    }

    console.error(`❌ All ${maxRetries} attempts failed:`, lastError.message)
    throw lastError
  }

  isRetryableError(error) {
    // Network errors
    if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') {
      return true
    }

    // Rate limiting
    if (error.error?.code === 'RATE_LIMIT_EXCEEDED') {
      return true
    }

    // Server errors
    if (error.status >= 500) {
      return true
    }

    return false
  }

  calculateDelay(attempt, error) {
    // Use retry-after header if available
    if (error.error?.details?.retry_after_seconds) {
      return error.error.details.retry_after_seconds * 1000
    }

    // Exponential backoff
    return Math.min(1000 * Math.pow(2, attempt - 1), 30000)
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}

Testing

Unit Tests with Jest

const { LoomAPI } = require('@loomapi/node-sdk')

describe('LoomService', () => {
  let loomService
  let mockLoom

  beforeEach(() => {
    mockLoom = {
      verify: jest.fn()
    }

    loomService = new LoomService('test-key')
    loomService.loom = mockLoom
  })

  test('successful verification', async () => {
    mockLoom.verify.mockResolvedValue({
      request_id: 'req_123',
      status: 'verified',
      verified_age: 25,
      confidence_score: 0.95
    })

    const result = await loomService.verifyWithRetry('base64data')

    expect(result.status).toBe('verified')
    expect(result.verified_age).toBe(25)
  })

  test('handles rate limiting with retry', async () => {
    mockLoom.verify
      .mockRejectedValueOnce({
        error: {
          code: 'RATE_LIMIT_EXCEEDED',
          details: { retry_after_seconds: 1 }
        }
      })
      .mockResolvedValueOnce({
        request_id: 'req_123',
        status: 'verified',
        verified_age: 25
      })

    const result = await loomService.verifyWithRetry('base64data')

    expect(mockLoom.verify).toHaveBeenCalledTimes(2)
    expect(result.status).toBe('verified')
  })

  test('fails after max retries', async () => {
    mockLoom.verify.mockRejectedValue(
      new Error('Network timeout')
    )

    await expect(loomService.verifyWithRetry('base64data', { maxRetries: 2 }))
      .rejects.toThrow('Network timeout')

    expect(mockLoom.verify).toHaveBeenCalledTimes(2)
  })
})

Integration Tests

describe('User Registration Integration', () => {
  let testUser
  let testDocument

  beforeEach(async () => {
    // Create test user
    testUser = await User.create({
      email: 'test@example.com',
      firstName: 'John',
      lastName: 'Doe'
    })

    // Load test document
    testDocument = await loadTestDocument('valid_passport.jpg')
  })

  test('complete registration flow', async () => {
    const registrationService = new UserRegistrationService()

    const result = await registrationService.registerUser({
      email: 'test@example.com',
      password: 'password123',
      firstName: 'John',
      lastName: 'Doe'
    }, testDocument)

    expect(result.success).toBe(true)
    expect(result.user.age).toBeGreaterThanOrEqual(18)
    expect(result.user.age_verified).toBe(true)

    // Verify database state
    const updatedUser = await User.findById(result.user.id)
    expect(updatedUser.age_verified).toBe(true)
    expect(updatedUser.verified_age).toBe(result.user.age)
  })

  test('handles verification service downtime', async () => {
    // Mock service failure
    jest.spyOn(LoomAPI.prototype, 'verify').mockRejectedValue(
      new Error('Service unavailable')
    )

    const registrationService = new UserRegistrationService()

    const result = await registrationService.registerUser({
      email: 'test@example.com',
      password: 'password123',
      firstName: 'John',
      lastName: 'Doe'
    }, testDocument)

    expect(result.success).toBe(true)
    expect(result.requires_manual_verification).toBe(true)

    // User should be created but not verified
    const user = await User.findOne({ email: 'test@example.com' })
    expect(user.age_verified).toBe(false)
    expect(user.verification_status).toBe('pending')
  })
})

Serverless (AWS Lambda)

Lambda Function

const { LoomAPI } = require('@loomapi/node-sdk')

const loom = new LoomAPI({
  apiKey: process.env.LOOM_API_KEY
})

exports.handler = async (event) => {
  console.log('Processing verification request')

  try {
    const { documentData, documentType = 'passport', userId } = JSON.parse(event.body)

    const result = await loom.verify({
      documentType,
      documentData,
      metadata: {
        user_id: userId,
        lambda_request_id: event.requestContext?.requestId,
        source: 'lambda'
      }
    })

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        success: result.status === 'verified',
        age: result.verified_age,
        confidence: result.confidence_score,
        requestId: result.request_id
      })
    }

  } catch (error) {
    console.error('Lambda error:', error)

    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        success: false,
        error: error.message
      })
    }
  }
}

API Gateway Integration

# serverless.yml or CloudFormation
functions:
  verifyAge:
    handler: handler.verifyAge
    events:
      - http:
          path: verify
          method: post
          cors: true
    environment:
      LOOM_API_KEY: ${env:LOOM_API_KEY}

Performance Optimization

Connection Pooling

const https = require('https')

// Create agent for connection reuse
const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 50,
  maxFreeSockets: 10
})

const loom = new LoomAPI({
  apiKey: process.env.LOOM_API_KEY,
  // Custom axios configuration
  axiosConfig: {
    httpsAgent: agent,
    timeout: 30000
  }
})

Caching

const NodeCache = require('node-cache')

class CachedLoomService {
  constructor(apiKey) {
    this.loom = new LoomAPI({ apiKey })
    this.cache = new NodeCache({ stdTTL: 3600 }) // 1 hour TTL
  }

  async verifyWithCache(documentData, cacheKey) {
    // Check cache first
    const cached = this.cache.get(cacheKey)
    if (cached) {
      console.log('🔄 Using cached result')
      return cached
    }

    // Perform verification
    const result = await this.loom.verify({
      documentType: 'passport',
      documentData
    })

    // Cache successful results
    if (result.status === 'verified') {
      this.cache.set(cacheKey, result)
    }

    return result
  }
}

Monitoring & Logging

Structured Logging

const winston = require('winston')

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'loom-verifications.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
})

class MonitoredLoomService {
  constructor(apiKey) {
    this.loom = new LoomAPI({ apiKey })
  }

  async verify(documentData, metadata = {}) {
    const startTime = Date.now()
    const requestId = metadata.request_id || `req_${Date.now()}`

    logger.info('Starting verification', {
      requestId,
      documentType: metadata.document_type || 'unknown',
      userId: metadata.user_id
    })

    try {
      const result = await this.loom.verify({
        documentType: metadata.document_type || 'passport',
        documentData,
        metadata: { ...metadata, request_id: requestId }
      })

      const duration = Date.now() - startTime

      logger.info('Verification completed', {
        requestId,
        status: result.status,
        age: result.verified_age,
        confidence: result.confidence_score,
        duration,
        userId: metadata.user_id
      })

      return result

    } catch (error) {
      const duration = Date.now() - startTime

      logger.error('Verification failed', {
        requestId,
        error: error.message,
        duration,
        userId: metadata.user_id,
        stack: error.stack
      })

      throw error
    }
  }
}

This comprehensive Node.js integration guide covers everything from basic usage to advanced patterns, error handling, testing, and production deployment scenarios.