無料で使えるAPI(2025年版)

windows JavaScript
スポンサーリンク

API一覧を取得→HTMLに自動表示するミニアプリ

See the Pen Public APIs Explorer by MONO365 -Color your days- (@monoqlo365) on CodePen.

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Public APIs Explorer — ミニアプリ</title>
  <style>
    :root{--bg:#0f1724;--card:#0b1220;--muted:#9aa4b2;--accent:#4f46e5}
    html,body{height:100%;margin:0;font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;color:#e6eef8;background:linear-gradient(180deg,#071029 0%,#07172a 100%)}
    .app{max-width:1100px;margin:28px auto;padding:20px}
    header{display:flex;align-items:center;gap:16px;margin-bottom:18px}
    h1{font-size:20px;margin:0}
    .controls{display:flex;gap:8px;flex-wrap:wrap}
    input[type=text],select{padding:10px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:rgba(255,255,255,0.02);color:inherit;min-width:180px}
    button{background:var(--accent);border:none;color:white;padding:10px 12px;border-radius:8px;cursor:pointer}
    .grid{display:grid;grid-template-columns:1fr;gap:12px}
    @media(min-width:760px){.grid{grid-template-columns:repeat(2,1fr)}}
    .card{background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));padding:12px;border-radius:12px;border:1px solid rgba(255,255,255,0.03);box-shadow:0 6px 18px rgba(2,6,23,0.6)}
    .card h3{margin:0 0 6px 0;font-size:16px}
    .meta{display:flex;gap:8px;flex-wrap:wrap;align-items:center;color:var(--muted);font-size:13px}
    .tag{background:rgba(255,255,255,0.03);padding:6px 8px;border-radius:999px;font-size:12px}
    footer{margin-top:18px;color:var(--muted);font-size:13px}
    .toolbar{display:flex;gap:8px;align-items:center}
    .pagination{display:flex;gap:6px;align-items:center}
    .pagebtn{background:transparent;border:1px solid rgba(255,255,255,0.04);padding:6px 8px;border-radius:6px;color:var(--muted);cursor:pointer}
    .small{font-size:13px;color:var(--muted)}
    .links{margin-top:8px;display:flex;gap:8px}
    a.btn-link{background:transparent;border:1px solid rgba(255,255,255,0.06);padding:6px 8px;border-radius:8px;color:inherit;text-decoration:none}
    .empty{padding:18px;border-radius:10px;border:1px dashed rgba(255,255,255,0.04);color:var(--muted)}
  </style>
</head>
<body>
  <div class="app" id="app">
    <header>
      <div>
        <h1>Public APIs Explorer</h1>
        <div class="small">https://api.publicapis.dev/entries を使って API 一覧を自動表示します</div>
      </div>
      <div style="flex:1"></div>
      <div class="toolbar">
        <button id="refresh">再読み込み</button>
      </div>
    </header>

    <section class="controls card">
      <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
        <input id="q" type="text" placeholder="検索(API名 / 説明)..." />
        <select id="category"><option value="">カテゴリで絞り込み(すべて)</option></select>
        <select id="auth"><option value="">認証(すべて)</option><option value="null">なし</option><option value="apiKey">API Key</option><option value="OAuth">OAuth</option></select>
        <select id="https"><option value="">HTTPS(すべて)</option><option value="true">あり</option><option value="false">なし</option></select>
        <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
          <label class="small">表示件数</label>
          <select id="perPage"><option>10</option><option>20</option><option>50</option></select>
        </div>
      </div>
    </section>

    <main id="list" class="grid" style="margin-top:14px"></main>

    <div style="display:flex;justify-content:space-between;align-items:center;margin-top:12px">
      <div id="summary" class="small"></div>
      <div class="pagination" id="pagination"></div>
    </div>

    <footer>
      <div class="small">Powered by <a href="https://api.publicapis.dev/entries" target="_blank">api.publicapis.dev</a>. このミニアプリはクライアント側のみで動作します。</div>
    </footer>
  </div>

  <script>
  // --- 設定 ---
  const API_URL = 'https://api.publicapis.dev/entries';

  // --- 状態 ---
  let entries = []; // 全件保存
  let filtered = []; // フィルタ後
  let page = 1;
  let perPage = 10;

  // --- 要素 ---
  const el = {
    list: document.getElementById('list'),
    q: document.getElementById('q'),
    category: document.getElementById('category'),
    auth: document.getElementById('auth'),
    https: document.getElementById('https'),
    perPage: document.getElementById('perPage'),
    refresh: document.getElementById('refresh'),
    summary: document.getElementById('summary'),
    pagination: document.getElementById('pagination')
  };

  // --- 初期化 ---
  async function init(){
    addEventListeners();
    perPage = Number(el.perPage.value);
    await loadData();
  }

  function addEventListeners(){
    el.q.addEventListener('input', onFilter);
    el.category.addEventListener('change', onFilter);
    el.auth.addEventListener('change', onFilter);
    el.https.addEventListener('change', onFilter);
    el.perPage.addEventListener('change', () => { perPage = Number(el.perPage.value); page = 1; render(); });
    el.refresh.addEventListener('click', () => loadData(true));
  }

  // --- データ取得 ---
  async function loadData(force=false){
    try{
      el.refresh.disabled = true;
      el.refresh.textContent = '読み込み中...';
      const res = await fetch(API_URL);
      if(!res.ok) throw new Error(res.status + ' ' + res.statusText);
      const json = await res.json();
      // API の返却形式: { count: number, entries: [...] } のはず
      entries = json.entries || json || [];
      // カテゴリをセット
      populateCategories(entries);
      page = 1;
      onFilter();
    }catch(err){
      el.list.innerHTML = `<div class="empty">データ取得に失敗しました: ${err.message}</div>`;
      console.error(err);
    }finally{
      el.refresh.disabled = false;
      el.refresh.textContent = '再読み込み';
    }
  }

  function populateCategories(list){
    const cats = Array.from(new Set(list.map(e => e.Category || e.category).filter(Boolean))).sort();
    el.category.innerHTML = '<option value="">カテゴリで絞り込み(すべて)</option>' + cats.map(c=>`<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join('');
  }

  // --- フィルタ ---
  function onFilter(){
    const q = el.q.value.trim().toLowerCase();
    const cat = el.category.value;
    const auth = el.auth.value;
    const https = el.https.value;

    filtered = entries.filter(e => {
      const name = (e.API || e.api || '').toLowerCase();
      const desc = (e.Description || e.description || '').toLowerCase();
      if(q && !(name.includes(q) || desc.includes(q))) return false;
      if(cat && (e.Category || e.category) !== cat) return false;
      if(auth){
        if(auth === 'null'){
          if((e.Auth || e.auth)) return false;
        } else {
          if((e.Auth || e.auth) !== auth) return false;
        }
      }
      if(https){
        const hasHttps = !!(e.HTTPS === true || String(e.HTTPS).toLowerCase()==='true');
        if(https === 'true' && !hasHttps) return false;
        if(https === 'false' && hasHttps) return false;
      }
      return true;
    });
    page = 1;
    render();
  }

  // --- レンダリング ---
  function render(){
    el.list.innerHTML = '';
    const total = filtered.length;
    const totalPages = Math.max(1, Math.ceil(total / perPage));
    if(page > totalPages) page = totalPages;
    const start = (page - 1) * perPage;
    const slice = filtered.slice(start, start + perPage);

    if(slice.length === 0){
      el.list.innerHTML = '<div class="empty">条件に一致するAPIがありません。</div>';
    } else {
      const frag = document.createDocumentFragment();
      slice.forEach(entry => frag.appendChild(renderCard(entry)));
      el.list.appendChild(frag);
    }

    // summary
    el.summary.textContent = `表示 ${start+1}${Math.min(start+slice.length,total)} / 全 ${total} 件`;

    // pagination
    renderPagination(totalPages);
  }

  function renderCard(e){
    const card = document.createElement('article');
    card.className = 'card';
    const title = document.createElement('h3');
    title.textContent = e.API || e.api || 'NO NAME';
    const desc = document.createElement('div');
    desc.textContent = e.Description || e.description || '';
    desc.style.margin = '8px 0';

    const meta = document.createElement('div');
    meta.className = 'meta';
    const auth = document.createElement('div'); auth.className='tag'; auth.textContent = `Auth: ${e.Auth || e.auth || 'None'}`;
    const https = document.createElement('div'); https.className='tag'; https.textContent = `HTTPS: ${e.HTTPS ? 'Yes' : 'No'}`;
    const cors = document.createElement('div'); cors.className='tag'; cors.textContent = `CORS: ${e.Cors || e.cors || 'unknown'}`;
    const category = document.createElement('div'); category.className='tag'; category.textContent = e.Category || e.category || '';

    const links = document.createElement('div'); links.className='links';
    if(e.Link || e.link) links.innerHTML += `<a class="btn-link" href="${escapeHtml(e.Link||e.link)}" target="_blank">公式</a>`;
    links.innerHTML += `<button class="pagebtn" data-url="${escapeHtml(e.Link||e.link||'')}">curl をコピー</button>`;

    meta.appendChild(auth); meta.appendChild(https); meta.appendChild(cors); meta.appendChild(category);

    card.appendChild(title); card.appendChild(desc); card.appendChild(meta); card.appendChild(links);

    // copy curl handler
    card.querySelector('.pagebtn').addEventListener('click', (ev)=>{
      const url = ev.currentTarget.dataset.url;
      const curl = url ? `curl -s "${url}"` : 'curl コマンドを生成できません (リンクがありません)';
      navigator.clipboard.writeText(curl).then(()=>{
        ev.currentTarget.textContent = 'コピーしました';
        setTimeout(()=> ev.currentTarget.textContent = 'curl をコピー', 1200);
      }).catch(()=> alert('クリップボードへの書き込みに失敗しました'));
    });

    return card;
  }

  function renderPagination(totalPages){
    el.pagination.innerHTML = '';
    const first = createPageBtn('<<', ()=>{ page=1; render(); });
    const prev = createPageBtn('<', ()=>{ if(page>1) page--; render(); });
    el.pagination.appendChild(first); el.pagination.appendChild(prev);
    // show pages with window
    const win = 5; const start = Math.max(1, page - Math.floor(win/2));
    for(let i = start; i <= Math.min(totalPages, start + win -1); i++){
      const btn = createPageBtn(i, ()=>{ page = i; render(); });
      if(i === page) btn.style.fontWeight = '600';
      el.pagination.appendChild(btn);
    }
    const next = createPageBtn('>', ()=>{ if(page<totalPages) page++; render(); });
    const last = createPageBtn('>>', ()=>{ page = totalPages; render(); });
    el.pagination.appendChild(next); el.pagination.appendChild(last);
  }

  function createPageBtn(label, onClick){
    const b = document.createElement('button'); b.className='pagebtn'; b.textContent = label; b.addEventListener('click', onClick); return b;
  }

  // --- ヘルパ ---
  function escapeHtml(s){ if(!s) return ''; return String(s).replace(/[&<>"']/g, c=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[c])); }

  // 起動
  init();
  </script>
</body>
</html>
HTML

機能のまとめ:

  • https://api.publicapis.dev/entries から一覧を取得して自動表示
  • 検索(API名・説明)・カテゴリ絞り込み・認証/HTTPSフィルタ・表示件数切替
  • ページネーション・各APIの公式リンクを開くボタン・curlコマンドのコピー機能
  • クライアント単体(バックエンド不要)
タイトルとURLをコピーしました