Back to Blog
Tutorial

How to Build an Influencer Database from Scratch with JavaScript

January 12, 2026
15 min read
S
By SociaVault Team
Influencer MarketingJavaScriptDatabaseTikTokInstagramYouTube

How to Build an Influencer Database from Scratch with JavaScript

You're running an influencer marketing agency. Or building a SaaS tool. Or just trying to find creators for your brand.

Either way, you need a database of influencers. Not a spreadsheet you manually update. A real, automated system that:

  • Discovers new creators automatically
  • Tracks follower growth over time
  • Stores engagement metrics across platforms
  • Lets you search and filter by niche, size, and performance

The problem? Influencer databases like Modash cost $200-1,000/month. CreatorIQ requires enterprise contracts. And manually tracking creators in Google Sheets makes you want to quit marketing entirely.

In this tutorial, I'll show you how to build your own influencer database using JavaScript, SQLite, and the SociaVault API. By the end, you'll have a system that rivals tools costing thousands per year.

Need data from multiple platforms? Learn how to scrape social media data reliably.

What We're Building

An influencer database system that:

  1. Collects profiles from TikTok, Instagram, and YouTube
  2. Stores data in a local SQLite database
  3. Tracks metrics over time (followers, engagement, posts)
  4. Searches and filters by criteria you define
  5. Exports lists for outreach campaigns

Tech Stack:

  • Node.js
  • SQLite (via better-sqlite3)
  • SociaVault API for data collection
  • Express for a simple web interface

Step 1: Project Setup

mkdir influencer-database
cd influencer-database
npm init -y
npm install axios better-sqlite3 express dotenv

Create .env:

SOCIAVAULT_API_KEY=your_api_key_here
PORT=3000

Step 2: Database Schema

Create database.js:

const Database = require('better-sqlite3');
const db = new Database('influencers.db');

// Initialize tables
db.exec(`
  CREATE TABLE IF NOT EXISTS influencers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    platform TEXT NOT NULL,
    username TEXT NOT NULL,
    display_name TEXT,
    bio TEXT,
    profile_url TEXT,
    profile_image TEXT,
    followers INTEGER DEFAULT 0,
    following INTEGER DEFAULT 0,
    posts_count INTEGER DEFAULT 0,
    engagement_rate REAL DEFAULT 0,
    avg_views INTEGER DEFAULT 0,
    avg_likes INTEGER DEFAULT 0,
    avg_comments INTEGER DEFAULT 0,
    niche TEXT,
    email TEXT,
    country TEXT,
    verified INTEGER DEFAULT 0,
    tier TEXT,
    last_updated TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(platform, username)
  );

  CREATE TABLE IF NOT EXISTS metrics_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    influencer_id INTEGER,
    followers INTEGER,
    engagement_rate REAL,
    avg_views INTEGER,
    recorded_at TEXT DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (influencer_id) REFERENCES influencers(id)
  );

  CREATE TABLE IF NOT EXISTS posts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    influencer_id INTEGER,
    platform TEXT,
    post_id TEXT,
    post_url TEXT,
    caption TEXT,
    views INTEGER DEFAULT 0,
    likes INTEGER DEFAULT 0,
    comments INTEGER DEFAULT 0,
    shares INTEGER DEFAULT 0,
    posted_at TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (influencer_id) REFERENCES influencers(id),
    UNIQUE(platform, post_id)
  );

  CREATE TABLE IF NOT EXISTS lists (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    description TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  );

  CREATE TABLE IF NOT EXISTS list_members (
    list_id INTEGER,
    influencer_id INTEGER,
    added_at TEXT DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (list_id, influencer_id),
    FOREIGN KEY (list_id) REFERENCES lists(id),
    FOREIGN KEY (influencer_id) REFERENCES influencers(id)
  );

  CREATE INDEX IF NOT EXISTS idx_influencers_platform ON influencers(platform);
  CREATE INDEX IF NOT EXISTS idx_influencers_followers ON influencers(followers);
  CREATE INDEX IF NOT EXISTS idx_influencers_niche ON influencers(niche);
  CREATE INDEX IF NOT EXISTS idx_influencers_tier ON influencers(tier);
`);

module.exports = db;

Step 3: Data Collection Module

Create collector.js:

require('dotenv').config();
const axios = require('axios');
const db = require('./database');

const API_BASE = 'https://api.sociavault.com/v1/scrape';
const headers = { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` };

// Determine influencer tier based on followers
function getTier(followers) {
  if (followers >= 1000000) return 'mega';
  if (followers >= 100000) return 'macro';
  if (followers >= 10000) return 'micro';
  if (followers >= 1000) return 'nano';
  return 'rising';
}

// Calculate engagement rate
function calculateEngagement(avgLikes, avgComments, followers) {
  if (!followers || followers === 0) return 0;
  return ((avgLikes + avgComments) / followers * 100).toFixed(2);
}

// ========== TikTok Collector ==========
async function collectTikTokProfile(username) {
  try {
    // Get profile
    const profileRes = await axios.get(`${API_BASE}/tiktok/profile`, {
      params: { username },
      headers
    });
    const profile = profileRes.data.data;

    // Get recent videos for engagement calculation
    const videosRes = await axios.get(`${API_BASE}/tiktok/videos`, {
      params: { username, count: 30 },
      headers
    });
    const videos = videosRes.data.data || [];

    // Calculate averages
    const avgViews = videos.length > 0 
      ? Math.round(videos.reduce((sum, v) => sum + (v.playCount || 0), 0) / videos.length)
      : 0;
    const avgLikes = videos.length > 0
      ? Math.round(videos.reduce((sum, v) => sum + (v.diggCount || 0), 0) / videos.length)
      : 0;
    const avgComments = videos.length > 0
      ? Math.round(videos.reduce((sum, v) => sum + (v.commentCount || 0), 0) / videos.length)
      : 0;

    const followers = profile.followerCount || profile.stats?.followerCount || 0;
    const engagementRate = calculateEngagement(avgLikes, avgComments, followers);

    const influencerData = {
      platform: 'tiktok',
      username: profile.uniqueId,
      display_name: profile.nickname,
      bio: profile.signature,
      profile_url: `https://tiktok.com/@${profile.uniqueId}`,
      profile_image: profile.avatarLarger || profile.avatarMedium,
      followers: followers,
      following: profile.followingCount || profile.stats?.followingCount || 0,
      posts_count: profile.videoCount || profile.stats?.videoCount || 0,
      engagement_rate: engagementRate,
      avg_views: avgViews,
      avg_likes: avgLikes,
      avg_comments: avgComments,
      verified: profile.verified ? 1 : 0,
      tier: getTier(followers),
      last_updated: new Date().toISOString()
    };

    // Upsert influencer
    const stmt = db.prepare(`
      INSERT INTO influencers (
        platform, username, display_name, bio, profile_url, profile_image,
        followers, following, posts_count, engagement_rate, avg_views,
        avg_likes, avg_comments, verified, tier, last_updated
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(platform, username) DO UPDATE SET
        display_name = excluded.display_name,
        bio = excluded.bio,
        profile_image = excluded.profile_image,
        followers = excluded.followers,
        following = excluded.following,
        posts_count = excluded.posts_count,
        engagement_rate = excluded.engagement_rate,
        avg_views = excluded.avg_views,
        avg_likes = excluded.avg_likes,
        avg_comments = excluded.avg_comments,
        verified = excluded.verified,
        tier = excluded.tier,
        last_updated = excluded.last_updated
    `);

    const result = stmt.run(
      influencerData.platform, influencerData.username, influencerData.display_name,
      influencerData.bio, influencerData.profile_url, influencerData.profile_image,
      influencerData.followers, influencerData.following, influencerData.posts_count,
      influencerData.engagement_rate, influencerData.avg_views, influencerData.avg_likes,
      influencerData.avg_comments, influencerData.verified, influencerData.tier,
      influencerData.last_updated
    );

    // Get the influencer ID
    const influencer = db.prepare(
      'SELECT id FROM influencers WHERE platform = ? AND username = ?'
    ).get('tiktok', profile.uniqueId);

    // Store metrics history
    db.prepare(`
      INSERT INTO metrics_history (influencer_id, followers, engagement_rate, avg_views)
      VALUES (?, ?, ?, ?)
    `).run(influencer.id, influencerData.followers, influencerData.engagement_rate, influencerData.avg_views);

    // Store recent posts
    const postStmt = db.prepare(`
      INSERT INTO posts (influencer_id, platform, post_id, post_url, caption, views, likes, comments, shares, posted_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(platform, post_id) DO UPDATE SET
        views = excluded.views,
        likes = excluded.likes,
        comments = excluded.comments,
        shares = excluded.shares
    `);

    for (const video of videos.slice(0, 10)) {
      postStmt.run(
        influencer.id,
        'tiktok',
        video.id,
        `https://tiktok.com/@${profile.uniqueId}/video/${video.id}`,
        video.desc?.substring(0, 500),
        video.playCount || 0,
        video.diggCount || 0,
        video.commentCount || 0,
        video.shareCount || 0,
        video.createTime ? new Date(video.createTime * 1000).toISOString() : null
      );
    }

    console.log(`✅ Collected TikTok: @${username} (${followers.toLocaleString()} followers)`);
    return influencerData;

  } catch (error) {
    console.error(`❌ Error collecting TikTok @${username}:`, error.message);
    return null;
  }
}

// ========== Instagram Collector ==========
async function collectInstagramProfile(username) {
  try {
    const profileRes = await axios.get(`${API_BASE}/instagram/profile`, {
      params: { username },
      headers
    });
    const profile = profileRes.data.data;

    // Get recent posts
    const postsRes = await axios.get(`${API_BASE}/instagram/posts`, {
      params: { username, count: 30 },
      headers
    });
    const posts = postsRes.data.data || [];

    const avgLikes = posts.length > 0
      ? Math.round(posts.reduce((sum, p) => sum + (p.like_count || p.likes || 0), 0) / posts.length)
      : 0;
    const avgComments = posts.length > 0
      ? Math.round(posts.reduce((sum, p) => sum + (p.comment_count || p.comments || 0), 0) / posts.length)
      : 0;
    const avgViews = posts.length > 0
      ? Math.round(posts.reduce((sum, p) => sum + (p.play_count || p.video_view_count || 0), 0) / posts.length)
      : 0;

    const followers = profile.follower_count || profile.followers || 0;
    const engagementRate = calculateEngagement(avgLikes, avgComments, followers);

    const influencerData = {
      platform: 'instagram',
      username: profile.username,
      display_name: profile.full_name,
      bio: profile.biography,
      profile_url: `https://instagram.com/${profile.username}`,
      profile_image: profile.profile_pic_url,
      followers: followers,
      following: profile.following_count || profile.following || 0,
      posts_count: profile.media_count || profile.posts || 0,
      engagement_rate: engagementRate,
      avg_views: avgViews,
      avg_likes: avgLikes,
      avg_comments: avgComments,
      verified: profile.is_verified ? 1 : 0,
      tier: getTier(followers),
      last_updated: new Date().toISOString()
    };

    // Upsert (same as TikTok)
    const stmt = db.prepare(`
      INSERT INTO influencers (
        platform, username, display_name, bio, profile_url, profile_image,
        followers, following, posts_count, engagement_rate, avg_views,
        avg_likes, avg_comments, verified, tier, last_updated
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(platform, username) DO UPDATE SET
        display_name = excluded.display_name,
        bio = excluded.bio,
        profile_image = excluded.profile_image,
        followers = excluded.followers,
        following = excluded.following,
        posts_count = excluded.posts_count,
        engagement_rate = excluded.engagement_rate,
        avg_views = excluded.avg_views,
        avg_likes = excluded.avg_likes,
        avg_comments = excluded.avg_comments,
        verified = excluded.verified,
        tier = excluded.tier,
        last_updated = excluded.last_updated
    `);

    stmt.run(
      influencerData.platform, influencerData.username, influencerData.display_name,
      influencerData.bio, influencerData.profile_url, influencerData.profile_image,
      influencerData.followers, influencerData.following, influencerData.posts_count,
      influencerData.engagement_rate, influencerData.avg_views, influencerData.avg_likes,
      influencerData.avg_comments, influencerData.verified, influencerData.tier,
      influencerData.last_updated
    );

    console.log(`✅ Collected Instagram: @${username} (${followers.toLocaleString()} followers)`);
    return influencerData;

  } catch (error) {
    console.error(`❌ Error collecting Instagram @${username}:`, error.message);
    return null;
  }
}

// ========== YouTube Collector ==========
async function collectYouTubeChannel(channelUrl) {
  try {
    const channelRes = await axios.get(`${API_BASE}/youtube/channel`, {
      params: { url: channelUrl },
      headers
    });
    const channel = channelRes.data.data;

    const subscribers = channel.subscriberCount || channel.subscribers || 0;
    const username = channel.customUrl || channel.handle || channel.channelId;

    const influencerData = {
      platform: 'youtube',
      username: username,
      display_name: channel.title || channel.name,
      bio: channel.description?.substring(0, 500),
      profile_url: channelUrl,
      profile_image: channel.thumbnail || channel.avatar,
      followers: subscribers,
      following: 0,
      posts_count: channel.videoCount || 0,
      engagement_rate: 0,
      avg_views: channel.averageViews || 0,
      avg_likes: 0,
      avg_comments: 0,
      verified: channel.isVerified ? 1 : 0,
      tier: getTier(subscribers),
      last_updated: new Date().toISOString()
    };

    const stmt = db.prepare(`
      INSERT INTO influencers (
        platform, username, display_name, bio, profile_url, profile_image,
        followers, following, posts_count, engagement_rate, avg_views,
        avg_likes, avg_comments, verified, tier, last_updated
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(platform, username) DO UPDATE SET
        display_name = excluded.display_name,
        bio = excluded.bio,
        profile_image = excluded.profile_image,
        followers = excluded.followers,
        posts_count = excluded.posts_count,
        avg_views = excluded.avg_views,
        verified = excluded.verified,
        tier = excluded.tier,
        last_updated = excluded.last_updated
    `);

    stmt.run(
      influencerData.platform, influencerData.username, influencerData.display_name,
      influencerData.bio, influencerData.profile_url, influencerData.profile_image,
      influencerData.followers, influencerData.following, influencerData.posts_count,
      influencerData.engagement_rate, influencerData.avg_views, influencerData.avg_likes,
      influencerData.avg_comments, influencerData.verified, influencerData.tier,
      influencerData.last_updated
    );

    console.log(`✅ Collected YouTube: ${channel.title} (${subscribers.toLocaleString()} subscribers)`);
    return influencerData;

  } catch (error) {
    console.error(`❌ Error collecting YouTube:`, error.message);
    return null;
  }
}

// ========== Bulk Collection ==========
async function collectBatch(usernames, platform, delayMs = 1000) {
  const results = [];
  
  for (const username of usernames) {
    let result;
    
    switch (platform) {
      case 'tiktok':
        result = await collectTikTokProfile(username);
        break;
      case 'instagram':
        result = await collectInstagramProfile(username);
        break;
      case 'youtube':
        result = await collectYouTubeChannel(username);
        break;
    }
    
    if (result) results.push(result);
    
    // Rate limiting
    await new Promise(resolve => setTimeout(resolve, delayMs));
  }
  
  return results;
}

module.exports = {
  collectTikTokProfile,
  collectInstagramProfile,
  collectYouTubeChannel,
  collectBatch
};

Step 4: Search & Filter Module

Create search.js:

const db = require('./database');

function searchInfluencers(filters = {}) {
  let query = 'SELECT * FROM influencers WHERE 1=1';
  const params = [];

  if (filters.platform) {
    query += ' AND platform = ?';
    params.push(filters.platform);
  }

  if (filters.minFollowers) {
    query += ' AND followers >= ?';
    params.push(filters.minFollowers);
  }

  if (filters.maxFollowers) {
    query += ' AND followers <= ?';
    params.push(filters.maxFollowers);
  }

  if (filters.tier) {
    query += ' AND tier = ?';
    params.push(filters.tier);
  }

  if (filters.minEngagement) {
    query += ' AND engagement_rate >= ?';
    params.push(filters.minEngagement);
  }

  if (filters.niche) {
    query += ' AND niche LIKE ?';
    params.push(`%${filters.niche}%`);
  }

  if (filters.keyword) {
    query += ' AND (bio LIKE ? OR display_name LIKE ? OR username LIKE ?)';
    params.push(`%${filters.keyword}%`, `%${filters.keyword}%`, `%${filters.keyword}%`);
  }

  if (filters.verified !== undefined) {
    query += ' AND verified = ?';
    params.push(filters.verified ? 1 : 0);
  }

  // Sorting
  const sortColumn = filters.sortBy || 'followers';
  const sortOrder = filters.sortOrder || 'DESC';
  query += ` ORDER BY ${sortColumn} ${sortOrder}`;

  // Pagination
  const limit = filters.limit || 50;
  const offset = filters.offset || 0;
  query += ' LIMIT ? OFFSET ?';
  params.push(limit, offset);

  return db.prepare(query).all(...params);
}

function getInfluencerStats() {
  const stats = db.prepare(`
    SELECT 
      COUNT(*) as total,
      COUNT(CASE WHEN platform = 'tiktok' THEN 1 END) as tiktok,
      COUNT(CASE WHEN platform = 'instagram' THEN 1 END) as instagram,
      COUNT(CASE WHEN platform = 'youtube' THEN 1 END) as youtube,
      COUNT(CASE WHEN tier = 'mega' THEN 1 END) as mega,
      COUNT(CASE WHEN tier = 'macro' THEN 1 END) as macro,
      COUNT(CASE WHEN tier = 'micro' THEN 1 END) as micro,
      COUNT(CASE WHEN tier = 'nano' THEN 1 END) as nano,
      AVG(engagement_rate) as avg_engagement,
      AVG(followers) as avg_followers
    FROM influencers
  `).get();

  return stats;
}

function getGrowthHistory(influencerId, days = 30) {
  return db.prepare(`
    SELECT * FROM metrics_history 
    WHERE influencer_id = ? 
    AND recorded_at >= datetime('now', '-${days} days')
    ORDER BY recorded_at ASC
  `).all(influencerId);
}

function exportToCSV(filters = {}) {
  const influencers = searchInfluencers({ ...filters, limit: 10000 });
  
  const headers = [
    'Platform', 'Username', 'Display Name', 'Followers', 'Engagement Rate',
    'Avg Views', 'Avg Likes', 'Posts', 'Tier', 'Verified', 'Bio', 'Profile URL'
  ];
  
  const rows = influencers.map(i => [
    i.platform,
    i.username,
    i.display_name,
    i.followers,
    i.engagement_rate,
    i.avg_views,
    i.avg_likes,
    i.posts_count,
    i.tier,
    i.verified ? 'Yes' : 'No',
    `"${(i.bio || '').replace(/"/g, '""')}"`,
    i.profile_url
  ]);
  
  return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
}

module.exports = {
  searchInfluencers,
  getInfluencerStats,
  getGrowthHistory,
  exportToCSV
};

Step 5: Web Interface

Create server.js:

require('dotenv').config();
const express = require('express');
const db = require('./database');
const { collectTikTokProfile, collectInstagramProfile, collectYouTubeChannel } = require('./collector');
const { searchInfluencers, getInfluencerStats, exportToCSV } = require('./search');

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

// Dashboard
app.get('/', (req, res) => {
  const stats = getInfluencerStats();
  
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Influencer Database</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; padding: 20px; }
    .container { max-width: 1400px; margin: 0 auto; }
    h1 { margin-bottom: 20px; }
    .stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; margin-bottom: 30px; }
    .stat { background: #1e293b; padding: 20px; border-radius: 12px; }
    .stat-value { font-size: 2rem; font-weight: bold; color: #3b82f6; }
    .stat-label { color: #94a3b8; font-size: 0.875rem; }
    .filters { background: #1e293b; padding: 20px; border-radius: 12px; margin-bottom: 20px; }
    .filter-row { display: flex; gap: 10px; flex-wrap: wrap; }
    input, select { padding: 10px; border: 1px solid #334155; border-radius: 6px; background: #0f172a; color: #fff; }
    button { padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 6px; cursor: pointer; }
    button:hover { background: #2563eb; }
    table { width: 100%; border-collapse: collapse; background: #1e293b; border-radius: 8px; overflow: hidden; }
    th, td { padding: 12px; text-align: left; border-bottom: 1px solid #334155; }
    th { background: #0f172a; }
    .tier { padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; }
    .tier-mega { background: #7c3aed; }
    .tier-macro { background: #3b82f6; }
    .tier-micro { background: #10b981; }
    .tier-nano { background: #f59e0b; }
    .add-form { background: #1e293b; padding: 20px; border-radius: 12px; margin-bottom: 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h1>📊 Influencer Database</h1>
    
    <div class="stats">
      <div class="stat"><div class="stat-value">${stats.total}</div><div class="stat-label">Total Influencers</div></div>
      <div class="stat"><div class="stat-value">${stats.tiktok}</div><div class="stat-label">TikTok</div></div>
      <div class="stat"><div class="stat-value">${stats.instagram}</div><div class="stat-label">Instagram</div></div>
      <div class="stat"><div class="stat-value">${stats.youtube}</div><div class="stat-label">YouTube</div></div>
      <div class="stat"><div class="stat-value">${(stats.avg_engagement || 0).toFixed(2)}%</div><div class="stat-label">Avg Engagement</div></div>
    </div>
    
    <div class="add-form">
      <h3 style="margin-bottom: 15px;">➕ Add Influencer</h3>
      <div class="filter-row">
        <select id="addPlatform">
          <option value="tiktok">TikTok</option>
          <option value="instagram">Instagram</option>
          <option value="youtube">YouTube</option>
        </select>
        <input type="text" id="addUsername" placeholder="Username or URL" style="flex: 1;">
        <button onclick="addInfluencer()">Add</button>
      </div>
    </div>
    
    <div class="filters">
      <h3 style="margin-bottom: 15px;">🔍 Search & Filter</h3>
      <div class="filter-row">
        <select id="filterPlatform">
          <option value="">All Platforms</option>
          <option value="tiktok">TikTok</option>
          <option value="instagram">Instagram</option>
          <option value="youtube">YouTube</option>
        </select>
        <select id="filterTier">
          <option value="">All Tiers</option>
          <option value="mega">Mega (1M+)</option>
          <option value="macro">Macro (100K+)</option>
          <option value="micro">Micro (10K+)</option>
          <option value="nano">Nano (1K+)</option>
        </select>
        <input type="text" id="filterKeyword" placeholder="Search bio or name...">
        <input type="number" id="minEngagement" placeholder="Min Engagement %" style="width: 150px;">
        <button onclick="applyFilters()">Search</button>
        <button onclick="exportCSV()" style="background: #10b981;">Export CSV</button>
      </div>
    </div>
    
    <table id="resultsTable">
      <thead>
        <tr>
          <th>Platform</th>
          <th>Username</th>
          <th>Followers</th>
          <th>Engagement</th>
          <th>Avg Views</th>
          <th>Tier</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody id="resultsBody"></tbody>
    </table>
  </div>
  
  <script>
    async function loadInfluencers(filters = {}) {
      const params = new URLSearchParams(filters);
      const res = await fetch('/api/influencers?' + params);
      const data = await res.json();
      
      document.getElementById('resultsBody').innerHTML = data.map(i => \`
        <tr>
          <td>\${i.platform}</td>
          <td><a href="\${i.profile_url}" target="_blank" style="color: #3b82f6;">@\${i.username}</a></td>
          <td>\${formatNumber(i.followers)}</td>
          <td>\${i.engagement_rate}%</td>
          <td>\${formatNumber(i.avg_views)}</td>
          <td><span class="tier tier-\${i.tier}">\${i.tier.toUpperCase()}</span></td>
          <td><button onclick="refreshInfluencer('\${i.platform}', '\${i.username}')" style="padding: 5px 10px; font-size: 12px;">🔄</button></td>
        </tr>
      \`).join('');
    }
    
    async function addInfluencer() {
      const platform = document.getElementById('addPlatform').value;
      const username = document.getElementById('addUsername').value;
      
      await fetch('/api/collect', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ platform, username })
      });
      
      document.getElementById('addUsername').value = '';
      loadInfluencers();
    }
    
    async function refreshInfluencer(platform, username) {
      await fetch('/api/collect', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ platform, username })
      });
      loadInfluencers();
    }
    
    function applyFilters() {
      const filters = {
        platform: document.getElementById('filterPlatform').value,
        tier: document.getElementById('filterTier').value,
        keyword: document.getElementById('filterKeyword').value,
        minEngagement: document.getElementById('minEngagement').value
      };
      loadInfluencers(filters);
    }
    
    async function exportCSV() {
      const filters = {
        platform: document.getElementById('filterPlatform').value,
        tier: document.getElementById('filterTier').value
      };
      const params = new URLSearchParams(filters);
      window.location.href = '/api/export?' + params;
    }
    
    function formatNumber(n) {
      if (n >= 1000000) return (n/1000000).toFixed(1) + 'M';
      if (n >= 1000) return (n/1000).toFixed(1) + 'K';
      return n || 0;
    }
    
    loadInfluencers();
  </script>
</body>
</html>
  `);
});

// API: Search influencers
app.get('/api/influencers', (req, res) => {
  const results = searchInfluencers(req.query);
  res.json(results);
});

// API: Collect influencer
app.post('/api/collect', async (req, res) => {
  const { platform, username } = req.body;
  
  let result;
  switch (platform) {
    case 'tiktok':
      result = await collectTikTokProfile(username);
      break;
    case 'instagram':
      result = await collectInstagramProfile(username);
      break;
    case 'youtube':
      result = await collectYouTubeChannel(username);
      break;
  }
  
  res.json({ success: !!result, data: result });
});

// API: Export CSV
app.get('/api/export', (req, res) => {
  const csv = exportToCSV(req.query);
  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename=influencers.csv');
  res.send(csv);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`\n📊 Influencer Database running at http://localhost:${PORT}\n`);
});

Step 6: Run It

node server.js

Open http://localhost:3000 and start building your database!

Sample Output

After adding a few influencers:

📊 Influencer Database Stats
═══════════════════════════════════════════════════════

Total Influencers: 47
├── TikTok: 23
├── Instagram: 18
├── YouTube: 6

By Tier:
├── Mega (1M+): 3
├── Macro (100K-1M): 12
├── Micro (10K-100K): 24
├── Nano (1K-10K): 8

Average Engagement: 4.7%

Top Performers:
1. @charlidamelio (TikTok) - 155M followers, 8.2% engagement
2. @khaby.lame (TikTok) - 162M followers, 6.1% engagement
3. @mrbeast (YouTube) - 245M subscribers

What You Just Built

Influencer database tools cost serious money:

  • Modash: $200-1,000/month
  • CreatorIQ: Enterprise pricing ($$$)
  • Upfluence: $795+/month

Your version does the same thing for API costs only.

Next Steps

  1. Add niche detection using bio keywords
  2. Build email finder integration
  3. Create automated discovery from hashtags
  4. Add competitor influencer tracking

Related: Learn how to find micro-influencers and vet for fake followers.


Ready to start building?

Get your free API key at sociavault.com and build the influencer database you've always wanted.

Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API. No credit card required.