JavaScript Examples

Introduction

This guide provides JavaScript examples for integrating Feedframer into your web applications using the Fetch API and other modern JavaScript techniques.

Basic Usage

Simple Fetch Request

const apiKey = 'YOUR_API_KEY';
const url = `https://feedframer.com/api/v1/me?api_key=${apiKey}&page[size]=12`;

fetch(url)
  .then(response => response.json())
  .then(data => {
    data.posts.forEach(post => {
      console.log(post.caption);
    });
  })
  .catch(error => console.error('Error:', error));

Async/Await

async function fetchPosts(apiKey) {
  try {
    const response = await fetch(
      `https://feedframer.com/api/v1/me?api_key=${apiKey}&page[size]=12`
    );

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const data = await response.json();
    return data.posts;
  } catch (error) {
    console.error('Error fetching posts:', error);
    return [];
  }
}

// Usage
const posts = await fetchPosts('YOUR_API_KEY');
posts.forEach(post => {
  console.log(post.caption);
});

Complete Client Class

class FeedframerClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://feedframer.com';
  }

  async getPosts(params = {}) {
    const defaultParams = {
      api_key: this.apiKey,
      'page[size]': 12
    };

    const queryParams = new URLSearchParams({
      ...defaultParams,
      ...params
    });

    try {
      const response = await fetch(
        `${this.baseUrl}/api/v1/me?${queryParams}`
      );

      if (!response.ok) {
        const error = await response.json();
        console.error('API Error:', error.errors[0]);
        return null;
      }

      return await response.json();
    } catch (error) {
      console.error('Request failed:', error);
      return null;
    }
  }

  async getImagePosts(limit = 12) {
    return await this.getPosts({
      'filter[type]': 'IMAGE',
      'page[size]': limit
    });
  }

  async getVideoPosts(limit = 12) {
    return await this.getPosts({
      'filter[type]': 'VIDEO',
      'page[size]': limit
    });
  }

  async getReels(limit = 12) {
    return await this.getPosts({
      'filter[type]': 'REELS',
      'page[size]': limit
    });
  }
}

// Usage
const client = new FeedframerClient('YOUR_API_KEY');
const data = await client.getImagePosts(12);

if (data) {
  data.posts.forEach(post => {
    console.log(post.caption);
  });
}

Error Handling

class FeedframerClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://feedframer.com';
  }

  async getPosts(params = {}) {
    const queryParams = new URLSearchParams({
      api_key: this.apiKey,
      'page[size]': 12,
      ...params
    });

    try {
      const response = await fetch(
        `${this.baseUrl}/api/v1/me?${queryParams}`
      );

      // Handle specific error codes
      if (response.status === 401) {
        throw new Error('Invalid API key');
      }

      if (response.status === 429) {
        throw new Error('Rate limit exceeded');
      }

      if (response.status === 422) {
        const error = await response.json();
        throw new Error(`Validation error: ${error.errors[0].detail}`);
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const data = await response.json();

      if (data.errors) {
        throw new Error(data.errors[0].detail);
      }

      return data;

    } catch (error) {
      console.error('Error:', error.message);
      throw error;
    }
  }
}

Caching

LocalStorage Caching

class CachedFeedframerClient extends FeedframerClient {
  constructor(apiKey, cacheDuration = 3600000) { // 1 hour default
    super(apiKey);
    this.cacheDuration = cacheDuration;
  }

  async getPosts(params = {}) {
    const cacheKey = this.getCacheKey(params);
    const cached = this.getCache(cacheKey);

    if (cached) {
      return cached;
    }

    const data = await super.getPosts(params);

    if (data) {
      this.setCache(cacheKey, data);
    }

    return data;
  }

  getCacheKey(params) {
    return `feedframer_${JSON.stringify(params)}`;
  }

  getCache(key) {
    try {
      const item = localStorage.getItem(key);

      if (!item) {
        return null;
      }

      const { data, timestamp } = JSON.parse(item);
      const age = Date.now() - timestamp;

      if (age > this.cacheDuration) {
        localStorage.removeItem(key);
        return null;
      }

      return data;
    } catch (error) {
      return null;
    }
  }

  setCache(key, data) {
    try {
      const item = {
        data,
        timestamp: Date.now()
      };

      localStorage.setItem(key, JSON.stringify(item));
    } catch (error) {
      console.error('Cache write failed:', error);
    }
  }

  clearCache() {
    const keys = Object.keys(localStorage);
    keys.forEach(key => {
      if (key.startsWith('feedframer_')) {
        localStorage.removeItem(key);
      }
    });
  }
}

// Usage
const client = new CachedFeedframerClient('YOUR_API_KEY');
const data = await client.getPosts(); // Fetches from API
const data2 = await client.getPosts(); // Returns from cache

In-Memory Caching

class MemoryCachedFeedframerClient extends FeedframerClient {
  constructor(apiKey, cacheDuration = 3600000) {
    super(apiKey);
    this.cache = new Map();
    this.cacheDuration = cacheDuration;
  }

  async getPosts(params = {}) {
    const cacheKey = JSON.stringify(params);

    if (this.cache.has(cacheKey)) {
      const { data, timestamp } = this.cache.get(cacheKey);
      const age = Date.now() - timestamp;

      if (age < this.cacheDuration) {
        return data;
      }

      this.cache.delete(cacheKey);
    }

    const data = await super.getPosts(params);

    if (data) {
      this.cache.set(cacheKey, {
        data,
        timestamp: Date.now()
      });
    }

    return data;
  }
}

DOM Rendering

async function renderInstagramFeed(apiKey, containerId) {
  const container = document.getElementById(containerId);

  // Show loading
  container.innerHTML = '<p>Loading Instagram posts...</p>';

  try {
    const client = new FeedframerClient(apiKey);
    const data = await client.getImagePosts(9);

    if (!data || !data.posts) {
      container.innerHTML = '<p>No posts found.</p>';
      return;
    }

    // Create grid
    const grid = document.createElement('div');
    grid.className = 'instagram-grid';
    grid.style.cssText = 'display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;';

    data.posts.forEach(post => {
      const item = document.createElement('div');
      item.className = 'instagram-post';

      const link = document.createElement('a');
      link.href = post.permalink;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';

      const img = document.createElement('img');
      img.src = post.mediaUrl;
      img.alt = post.caption || 'Instagram post';
      img.style.cssText = 'width: 100%; height: auto; display: block; border-radius: 8px;';

      link.appendChild(img);
      item.appendChild(link);
      grid.appendChild(item);
    });

    container.innerHTML = '';
    container.appendChild(grid);

  } catch (error) {
    container.innerHTML = `<p>Error loading posts: ${error.message}</p>`;
  }
}

// Usage
renderInstagramFeed('YOUR_API_KEY', 'instagram-feed');

Pagination

Load More Button

class PaginatedFeedframerClient extends FeedframerClient {
  constructor(apiKey) {
    super(apiKey);
    this.cursor = null;
  }

  async getNextPage(params = {}) {
    if (this.cursor) {
      params['page[cursor]'] = this.cursor;
    }

    const data = await this.getPosts(params);

    if (data) {
      this.cursor = data.meta.next_cursor;
    }

    return data;
  }

  hasMore() {
    return this.cursor !== null;
  }

  reset() {
    this.cursor = null;
  }
}

// Usage with DOM
let currentPosts = [];

async function loadMore() {
  const button = document.getElementById('load-more');
  button.disabled = true;
  button.textContent = 'Loading...';

  const client = new PaginatedFeedframerClient('YOUR_API_KEY');
  const data = await client.getNextPage({ 'page[size]': 12 });

  if (data) {
    currentPosts = [...currentPosts, ...data.posts];
    renderPosts(currentPosts);

    if (!client.hasMore()) {
      button.style.display = 'none';
    } else {
      button.disabled = false;
      button.textContent = 'Load More';
    }
  }
}

Infinite Scroll

class InfiniteScrollFeedframerClient extends FeedframerClient {
  constructor(apiKey) {
    super(apiKey);
    this.cursor = null;
    this.loading = false;
  }

  async loadNext(params = {}) {
    if (this.loading || this.cursor === null && this.cursor !== undefined) {
      return null;
    }

    this.loading = true;

    if (this.cursor) {
      params['page[cursor]'] = this.cursor;
    }

    const data = await this.getPosts(params);

    if (data) {
      this.cursor = data.meta.next_cursor;
    }

    this.loading = false;
    return data;
  }
}

// Setup infinite scroll
const client = new InfiniteScrollFeedframerClient('YOUR_API_KEY');

window.addEventListener('scroll', async () => {
  if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
    const data = await client.loadNext({ 'page[size]': 12 });

    if (data) {
      appendPosts(data.posts);
    }
  }
});

GraphQL

async function fetchPostsGraphQL(apiKey, limit = 12) {
  const query = `
    query GetPosts($first: Int!) {
      posts(first: $first) {
        data {
          id
          caption
          mediaUrl
          publishedAt
          instagramAccount {
            username
          }
        }
      }
    }
  `;

  const variables = {
    first: limit
  };

  const response = await fetch(
    `https://feedframer.com/graphql?api_key=${apiKey}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        query,
        variables
      })
    }
  );

  const result = await response.json();

  if (result.errors) {
    console.error('GraphQL errors:', result.errors);
    return null;
  }

  return result.data.posts.data;
}

// Usage
const posts = await fetchPostsGraphQL('YOUR_API_KEY', 12);

React Hook

import { useState, useEffect } from 'react';

function useFeedframer(apiKey, params = {}) {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchPosts() {
      try {
        setLoading(true);

        const queryParams = new URLSearchParams({
          api_key: apiKey,
          'page[size]': 12,
          ...params
        });

        const response = await fetch(
          `https://feedframer.com/api/v1/me?${queryParams}`
        );

        if (!response.ok) {
          throw new Error('Failed to fetch posts');
        }

        const data = await response.json();
        setPosts(data.posts);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchPosts();
  }, [apiKey, JSON.stringify(params)]);

  return { posts, loading, error };
}

// Usage in React component
function InstagramFeed() {
  const { posts, loading, error } = useFeedframer('YOUR_API_KEY', {
    'filter[type]': 'IMAGE'
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="instagram-grid">
      {posts.map(post => (
        <img key={post.id} src={post.mediaUrl} alt="" />
      ))}
    </div>
  );
}

TypeScript

interface Post {
  id: string;
  caption: string | null;
  mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM' | 'REELS';
  mediaUrl: string;
  thumbnailUrl: string | null;
  permalink: string;
  timestamp: string;
}

interface FeedframerResponse {
  username: string;
  posts: Post[];
  pagination: {
    nextCursor: string | null;
    hasMore: boolean;
    perPage: number;
  };
}

class FeedframerClient {
  private apiKey: string;
  private baseUrl: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://feedframer.com';
  }

  async getPosts(params: Record<string, any> = {}): Promise<FeedframerResponse | null> {
    const queryParams = new URLSearchParams({
      api_key: this.apiKey,
      'page[size]': '12',
      ...params
    });

    try {
      const response = await fetch(
        `${this.baseUrl}/api/v1/me?${queryParams}`
      );

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      console.error('Error:', error);
      return null;
    }
  }
}

Next Steps