Squad Golf LogoSQUAD GOLF

Squad Golf

Simple golf groups for recurring pool creation and friendly competition.

Platform

  • Pools
  • Groups
  • Tournaments

Developer

  • API Platform
  • Documentation
  • Pricing

Support

  • Coming Soon

Ā© 2024 Squad Golf. All rights reserved.

Code Examples & Use Cases

Real-world examples showing how to integrate the Squad Golf API into your applications. From simple widgets to complex real-time dashboards.

Popular Use Cases

Live Tournament Tracker
Intermediate
Build a real-time tournament leaderboard with WebSocket updates

Key Features

Real-time updatesPlayer searchTournament filtering

Technologies

ReactWebSocketTypeScript
Golf Pool Application
Advanced
Create fantasy golf pools where users pick players and compete

Key Features

User authenticationPlayer selectionScoring system

Technologies

Next.jsDatabaseReal-time updates
Player Statistics Dashboard
Beginner
Analyze player performance with comprehensive statistics

Key Features

Data visualizationHistorical dataRankings

Technologies

Chart.jsReactAPI Integration
Golf News Widget
Beginner
Embed live tournament information in your website

Key Features

Embeddable widgetCustomizable stylingAuto-refresh

Technologies

Vanilla JSCSSHTML
Live Tournament Leaderboard
Intermediate
Real-time leaderboard with WebSocket updates
react
import React from 'react';
import { SquadGolfProvider, useLeaderboard } from '@squad-golf/api-client/react';

// Initialize client outside component to avoid recreating
const client = new SquadGolfClient({
  apiKey: process.env.NEXT_PUBLIC_SQUAD_GOLF_API_KEY,
  enableWebSocket: true
});

function App() {
  return (
    <SquadGolfProvider client={client}>
      <LiveLeaderboard tournamentId="t_masters2024" />
    </SquadGolfProvider>
  );
}

function LiveLeaderboard({ tournamentId }) {
  const { 
    leaderboard, 
    isLive, 
    lastUpdated, 
    error, 
    loading 
  } = useLeaderboard(tournamentId);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!leaderboard) return <div>No leaderboard data available</div>;
  
  return (
    <div className="leaderboard-container">
      <div className="leaderboard-header">
        <h1>Live Leaderboard</h1>
        <div className="status-indicators">
          {isLive && (
            <span className="live-indicator">
              šŸ”“ LIVE
            </span>
          )}
          <span className="last-updated">
            Updated: {lastUpdated?.toLocaleTimeString()}
          </span>
        </div>
      </div>
      
      <div className="leaderboard-table">
        <div className="table-header">
          <span>Pos</span>
          <span>Player</span>
          <span>Score</span>
          <span>Total</span>
          <span>Thru</span>
        </div>
        
        {leaderboard.players.map((player, index) => (
          <div 
            key={player.player._id} 
            className={`player-row ${index < 3 ? 'top-three' : ''}`}
          >
            <span className="position">
              {player.position === 1 ? 'šŸ†' : player.position}
            </span>
            <div className="player-info">
              <span className="player-name">{player.player.fullName}</span>
              <span className="player-country">{player.player.country}</span>
            </div>
            <span className={`score ${player.score < 0 ? 'under-par' : player.score > 0 ? 'over-par' : 'even'}`}>
              {player.score === 0 ? 'E' : 
               player.score > 0 ? `+${player.score}` : 
               player.score}
            </span>
            <span className="total">{player.total}</span>
            <span className="thru">{player.thru || 'F'}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function LoadingSpinner() {
  return (
    <div className="loading-container">
      <div className="spinner"></div>
      <p>Loading leaderboard...</p>
    </div>
  );
}

function ErrorMessage({ error }) {
  return (
    <div className="error-container">
      <h3>Unable to load leaderboard</h3>
      <p>{error.message}</p>
      <button onClick={() => window.location.reload()}>
        Try Again
      </button>
    </div>
  );
}
Player Search with Autocomplete
Beginner
Searchable player directory with real-time suggestions
react
import React, { useState, useEffect, useCallback } from 'react';
import { SquadGolfClient } from '@squad-golf/api-client';

const client = new SquadGolfClient({
  apiKey: process.env.NEXT_PUBLIC_SQUAD_GOLF_API_KEY
});

function PlayerSearch() {
  const [query, setQuery] = useState('');
  const [players, setPlayers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [selectedPlayer, setSelectedPlayer] = useState(null);
  
  // Debounced search function
  const searchPlayers = useCallback(
    debounce(async (searchQuery) => {
      if (!searchQuery.trim()) {
        setPlayers([]);
        return;
      }
      
      setLoading(true);
      try {
        const results = await client.players.searchPlayers({
          name: searchQuery,
          limit: 10
        });
        setPlayers(results);
      } catch (error) {
        console.error('Search failed:', error);
        setPlayers([]);
      } finally {
        setLoading(false);
      }
    }, 300),
    []
  );
  
  useEffect(() => {
    searchPlayers(query);
  }, [query, searchPlayers]);
  
  const handlePlayerSelect = async (player) => {
    setSelectedPlayer(null);
    setLoading(true);
    
    try {
      // Get detailed player information
      const detailedPlayer = await client.players.getPlayer(player._id);
      setSelectedPlayer(detailedPlayer);
    } catch (error) {
      console.error('Failed to load player details:', error);
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div className="player-search-container">
      <div className="search-section">
        <h2>Player Search</h2>
        <div className="search-input-container">
          <input
            type="text"
            placeholder="Search for players..."
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            className="search-input"
          />
          {loading && <div className="search-spinner">šŸ”</div>}
        </div>
        
        {query && players.length > 0 && (
          <div className="search-results">
            {players.map((player) => (
              <div
                key={player._id}
                className="search-result-item"
                onClick={() => handlePlayerSelect(player)}
              >
                <div className="player-basic-info">
                  <span className="player-name">{player.fullName}</span>
                  <span className="player-country">
                    {player.countryFlag} {player.country}
                  </span>
                </div>
                {player.currentRankings?.owgr && (
                  <div className="player-ranking">
                    <span className="owgr-rank">
                      OWGR #{player.currentRankings.owgr.rank}
                    </span>
                  </div>
                )}
              </div>
            ))}
          </div>
        )}
        
        {query && players.length === 0 && !loading && (
          <div className="no-results">
            No players found for "{query}"
          </div>
        )}
      </div>
      
      {selectedPlayer && (
        <PlayerProfile player={selectedPlayer} />
      )}
    </div>
  );
}

function PlayerProfile({ player }) {
  return (
    <div className="player-profile">
      <div className="profile-header">
        <h3>{player.fullName}</h3>
        <span className="country">
          {player.countryFlag} {player.country}
        </span>
      </div>
      
      <div className="profile-stats">
        <div className="stat-grid">
          {player.currentRankings?.owgr && (
            <div className="stat-item">
              <span className="stat-label">OWGR Rank</span>
              <span className="stat-value">#{player.currentRankings.owgr.rank}</span>
            </div>
          )}
          
          {player.currentRankings?.fedexCup && (
            <div className="stat-item">
              <span className="stat-label">FedEx Cup Rank</span>
              <span className="stat-value">#{player.currentRankings.fedexCup.rank}</span>
            </div>
          )}
          
          {player.careerStats?.pgatourWins && (
            <div className="stat-item">
              <span className="stat-label">PGA Tour Wins</span>
              <span className="stat-value">{player.careerStats.pgatourWins}</span>
            </div>
          )}
          
          {player.careerStats?.majorWins && (
            <div className="stat-item">
              <span className="stat-label">Major Wins</span>
              <span className="stat-value">{player.careerStats.majorWins}</span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Utility function for debouncing
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

export default PlayerSearch;
Embeddable Tournament Widget
Beginner
Lightweight widget for displaying tournament information
javascript
class SquadGolfWidget {
  constructor(options) {
    this.apiKey = options.apiKey;
    this.container = options.container;
    this.tournamentId = options.tournamentId;
    this.refreshInterval = options.refreshInterval || 60000; // 1 minute
    this.baseURL = 'https://api.squad.golf';
    
    this.init();
  }
  
  async init() {
    this.render();
    await this.loadTournament();
    this.startAutoRefresh();
  }
  
  render() {
    this.container.innerHTML = `
      <div class="squad-golf-widget">
        <div class="widget-header">
          <h3 class="tournament-name">Loading...</h3>
          <div class="widget-status">
            <span class="status-indicator"></span>
            <span class="last-updated">Last updated: --</span>
          </div>
        </div>
        <div class="widget-content">
          <div class="loading-message">Loading tournament data...</div>
        </div>
        <div class="widget-footer">
          <span class="powered-by">Powered by Squad Golf API</span>
        </div>
      </div>
    `;
    
    this.addStyles();
  }
  
  async loadTournament() {
    try {
      const response = await fetch(`${this.baseURL}/api/v1/tournaments/${this.tournamentId}`, {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json'
        }
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const data = await response.json();
      this.updateWidget(data.data);
      
    } catch (error) {
      console.error('Failed to load tournament:', error);
      this.showError(error.message);
    }
  }
  
  updateWidget(tournament) {
    const nameEl = this.container.querySelector('.tournament-name');
    const statusEl = this.container.querySelector('.status-indicator');
    const updatedEl = this.container.querySelector('.last-updated');
    const contentEl = this.container.querySelector('.widget-content');
    
    // Update header
    nameEl.textContent = tournament.name;
    statusEl.className = `status-indicator ${tournament.status}`;
    statusEl.textContent = this.formatStatus(tournament.status);
    updatedEl.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
    
    // Update content
    if (tournament.leaderboard && tournament.leaderboard.length > 0) {
      contentEl.innerHTML = this.renderLeaderboard(tournament.leaderboard.slice(0, 5));
    } else {
      contentEl.innerHTML = this.renderTournamentInfo(tournament);
    }
  }
  
  renderLeaderboard(players) {
    return `
      <div class="leaderboard-mini">
        <div class="leaderboard-header">
          <span>Pos</span>
          <span>Player</span>
          <span>Score</span>
        </div>
        ${players.map(player => `
          <div class="leaderboard-row">
            <span class="position">${player.position}</span>
            <span class="player">${player.player.fullName}</span>
            <span class="score ${player.score < 0 ? 'under-par' : player.score > 0 ? 'over-par' : 'even'}">
              ${player.score === 0 ? 'E' : 
                player.score > 0 ? `+${player.score}` : 
                player.score}
            </span>
          </div>
        `).join('')}
      </div>
    `;
  }
  
  renderTournamentInfo(tournament) {
    const startDate = new Date(tournament.startDate).toLocaleDateString();
    const endDate = new Date(tournament.endDate).toLocaleDateString();
    
    return `
      <div class="tournament-info">
        <div class="info-item">
          <span class="label">Dates:</span>
          <span class="value">${startDate} - ${endDate}</span>
        </div>
        ${tournament.location ? `
          <div class="info-item">
            <span class="label">Location:</span>
            <span class="value">${tournament.location.name}</span>
          </div>
        ` : ''}
        ${tournament.fieldSize ? `
          <div class="info-item">
            <span class="label">Field Size:</span>
            <span class="value">${tournament.fieldSize} players</span>
          </div>
        ` : ''}
      </div>
    `;
  }
  
  formatStatus(status) {
    const statusMap = {
      'upcoming': 'Upcoming',
      'active': 'In Progress',
      'completed': 'Completed',
      'cancelled': 'Cancelled'
    };
    return statusMap[status] || status;
  }
  
  showError(message) {
    const contentEl = this.container.querySelector('.widget-content');
    contentEl.innerHTML = `
      <div class="error-message">
        <span class="error-icon">āš ļø</span>
        <span class="error-text">Failed to load tournament data</span>
        <small>${message}</small>
      </div>
    `;
  }
  
  startAutoRefresh() {
    setInterval(() => {
      this.loadTournament();
    }, this.refreshInterval);
  }
  
  addStyles() {
    if (document.getElementById('squad-golf-widget-styles')) return;
    
    const styles = document.createElement('style');
    styles.id = 'squad-golf-widget-styles';
    styles.textContent = `
      .squad-golf-widget {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        background: white;
        border: 1px solid #E8EDE3;
        border-radius: 8px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        overflow: hidden;
        max-width: 400px;
      }
      
      .widget-header {
        background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
        color: white;
        padding: 15px;
      }
      
      .tournament-name {
        margin: 0 0 8px 0;
        font-size: 16px;
        font-weight: 600;
      }
      
      .widget-status {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 12px;
        opacity: 0.9;
      }
      
      .status-indicator {
        padding: 2px 6px;
        border-radius: 10px;
        background: rgba(255, 255, 255, 0.2);
        font-size: 10px;
        text-transform: uppercase;
        font-weight: 500;
      }
      
      .status-indicator.active {
        background: #16A34A;
      }
      
      .widget-content {
        padding: 15px;
        min-height: 100px;
      }
      
      .loading-message, .error-message {
        text-align: center;
        color: #5C5F56;
        font-size: 14px;
      }
      
      .error-message {
        display: flex;
        flex-direction: column;
        gap: 8px;
        align-items: center;
      }
      
      .error-message small {
        color: #999;
        font-size: 12px;
      }
      
      .leaderboard-mini {
        font-size: 14px;
      }
      
      .leaderboard-header {
        display: grid;
        grid-template-columns: 40px 1fr 60px;
        gap: 8px;
        padding: 8px 0;
        border-bottom: 1px solid #E8EDE3;
        font-weight: 600;
        color: #5C5F56;
        font-size: 12px;
        text-transform: uppercase;
      }
      
      .leaderboard-row {
        display: grid;
        grid-template-columns: 40px 1fr 60px;
        gap: 8px;
        padding: 8px 0;
        border-bottom: 1px solid #F5F5F3;
      }
      
      .leaderboard-row:last-child {
        border-bottom: none;
      }
      
      .position {
        font-weight: 600;
        text-align: center;
      }
      
      .score {
        text-align: center;
        font-weight: 600;
      }
      
      .score.under-par { color: #16A34A; }
      .score.over-par { color: #DC2626; }
      .score.even { color: #2D3027; }
      
      .tournament-info {
        display: flex;
        flex-direction: column;
        gap: 8px;
      }
      
      .info-item {
        display: flex;
        justify-content: space-between;
        font-size: 14px;
      }
      
      .info-item .label {
        color: #5C5F56;
        font-weight: 500;
      }
      
      .info-item .value {
        color: #2D3027;
      }
      
      .widget-footer {
        background: #F5F5F3;
        padding: 8px 15px;
        text-align: center;
      }
      
      .powered-by {
        font-size: 11px;
        color: #5C5F56;
      }
    `;
    
    document.head.appendChild(styles);
  }
  
  destroy() {
    if (this.refreshTimer) {
      clearInterval(this.refreshTimer);
    }
    this.container.innerHTML = '';
  }
}

// Usage example:
// const widget = new SquadGolfWidget({
//   apiKey: 'your-api-key',
//   container: document.getElementById('tournament-widget'),
//   tournamentId: 't_masters2024',
//   refreshInterval: 30000 // 30 seconds
// });
Integration Best Practices
Tips for building robust applications with the Squad Golf API

Performance

  • • Use the TypeScript client's built-in caching
  • • Implement debouncing for search functionality
  • • Cache tournament data locally for better UX
  • • Use WebSocket for real-time updates when possible

Error Handling

  • • Always handle network failures gracefully
  • • Implement retry logic with exponential backoff
  • • Show loading states and error messages
  • • Fall back to cached data when possible

Security

  • • Never expose API keys in client-side code
  • • Use environment variables for configuration
  • • Implement proper CORS settings
  • • Monitor API usage and set up alerts

User Experience

  • • Show real-time indicators for live events
  • • Implement smooth loading transitions
  • • Provide search functionality with autocomplete
  • • Use consistent visual hierarchy
Additional Resources
Helpful links and tools for your development journey
TypeScript ClientOfficial TypeScript/JavaScript client libraryAPI ReferenceComplete endpoint documentationGet API KeyStart building with your API key