인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
博客园 - 叶小钗
MyScale Blog
MyScale Blog
V
Visual Studio Blog
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
I
InfoQ
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
5분 안에 임상 트라이얼 검색 앱을 만드는 방법 - 임상 트라이얼 API 튜토리얼
Rahul Ban · 2026-05-24 · via DEV Community

Rahul Ban

우리가 만드는 것

다음 5분 안에 사용자가 질환과 단계별로 임상 시험을 검색하고 카드에 결과를 표시하고 결과를 페이징하는 간단한 웹 앱을 만들게 됩니다.Clinical Trials API는 ClinicalTrials.gov을 개발자 친화적인 인터페이스로 감싸는 REST API입니다. 구조화된 자격 요건 기준(나이, 성별, 상태, 검체 값과 같은 카테고리로 파싱됨 - 정규식이 필요 없음), 위도/경도를 이용한 지리적 반경 검색, 새로운 시험을 위한 HTTP 웹훅 알림, 커서 기반 페이징, 집계 통계를 제공합니다. 신용카드 없이 무료 티어 제공. FDA에서 매일 업데이트되는 220개국 이상의 480,000개 이상의 시험. 모든 것이 하나의 API로 구동됩니다. 기본적인 HTML/JavaScript 지식과 무료 RapidAPI 계정이 필요합니다.

단계 1: API 키를 얻으시오 (30초)

  1. 가서https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api
  2. 무료 플랜에서 "구독"을 클릭하세요 (신용카드가 필요 없습니다)
  3. 대시보드에서 X-RapidAPI-Key를 복사하세요

그것이 다입니다. API 호출을 준비되었습니다.

단계 2: 첫 번째 API 호출을 만드세요

터미널을 엽니다 및 이 curl 명령어를 실행하세요 (YOUR_API_KEY를 바꿔주세요):

curl --request GET \
  --url 'https://clinical-trials-api.p.rapidapi.com/v1/trials/search?query=breast+cancer&phase=Phase+2&status=Recruiting&limit=3' \
  --header 'X-RapidAPI-Key: YOUR_API_KEY'

전체 화면 모드 입력 전체 화면 모드 종료

다음과 같은 내용이 보일 것입니다:

{
  "success": true,
  "data": [
    {
      "nct_id": "NCT04589845",
      "title": "Atezolizumab and Chemotherapy for Triple-Negative Breast Cancer",
      "brief_summary": "This phase II trial studies how well atezolizumab works with chemotherapy...",
      "condition": "Breast Cancer",
      "phase": "Phase 2",
      "status": "Recruiting",
      "enrollment_count": 280,
      "last_updated": "2026-05-15",
      "locations": [
        {
          "facility": "Dana-Farber Cancer Institute",
          "city": "Boston",
          "state": "MA",
          "country": "United States"
        }
      ]
    }
  ],
  "pagination": {
    "total": 847,
    "limit": 3,
    "next_cursor": "eyJvZmZzZXQiOjN9",
    "has_more": true
  }
}

전체 화면 모드 입력 전체 화면 모드 종료

847개의 일치 트라이얼이 정리된 JSON 형식으로 내부 페이징과 함께 반환됩니다. 스크래핑, 파싱, 파이프라인 유지보수 없습니다.

3단계: 테스트 검색 앱 만들기

단일 HTML 파일 (index.html)을 만들고 이 코드를 붙여넣으세요:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Clinical Trial Search</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; background: #f5f5f5; }
        h1 { color: #1a1a2e; }
        .search-box { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
        input, select, button { padding: 10px 15px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
        input { flex: 1; min-width: 200px; }
        button { background: #1a56db; color: white; border: none; cursor: pointer; font-weight: 600; }
        button:hover { background: #1647b8; }
        .trial-card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
        .trial-card h3 { margin: 0 0 8px 0; color: #1a1a2e; font-size: 16px; }
        .trial-meta { display: flex; gap: 15px; font-size: 13px; color: #666; margin-bottom: 8px; flex-wrap: wrap; }
        .badge { padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; }
        .badge-recruiting { background: #d4edda; color: #155724; }
        .badge-phase { background: #cce5ff; color: #004085; }
        .summary { font-size: 14px; color: #444; line-height: 1.5; }
        .pagination { display: flex; gap: 10px; align-items: center; margin-top: 20px; justify-content: center; }
        .loading { text-align: center; padding: 40px; color: #666; }
        .error { text-align: center; padding: 20px; color: #dc3545; background: #f8d7da; border-radius: 8px; }
        .nct { color: #1a56db; font-family: monospace; font-size: 14px; }
        .location { font-size: 12px; color: #888; margin-top: 6px; }
    </style>
</head>
<body>
    <h1>Clinical Trial Search</h1>
    <p style="color: #666; margin-bottom: 20px;">Powered by <a href="https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api">Clinical Trials API</a> -- 480K+ FDA trials, free tier, no credit card.</p>

    <div class="search-box">
        <input type="text" id="query" placeholder="Search condition or keyword (e.g., breast cancer)" value="breast cancer">
        <select id="phase">
            <option value="">All Phases</option>
            <option value="Phase 1">Phase 1</option>
            <option value="Phase 2" selected>Phase 2</option>
            <option value="Phase 3">Phase 3</option>
            <option value="Phase 4">Phase 4</option>
        </select>
        <select id="status">
            <option value="">All Statuses</option>
            <option value="Recruiting" selected>Recruiting</option>
            <option value="Not yet recruiting">Not Yet Recruiting</option>
            <option value="Active not recruiting">Active, Not Recruiting</option>
            <option value="Completed">Completed</option>
        </select>
        <button onclick="searchTrials()">Search</button>
    </div>

    <div id="results"></div>

    <script>
        const API_KEY = 'YOUR_RAPIDAPI_KEY'; // Replace with your key
        const BASE_URL = 'https://clinical-trials-api.p.rapidapi.com';

        let currentCursor = null;
        let currentQuery = null;

        async function searchTrials(cursor = null) {
            const query = document.getElementById('query').value;
            const phase = document.getElementById('phase').value;
            const status = document.getElementById('status').value;

            const params = new URLSearchParams({ query, limit: '5' });
            if (phase) params.append('phase', phase);
            if (status) params.append('status', status);
            if (cursor) params.append('cursor', cursor);

            const resultsDiv = document.getElementById('results');
            resultsDiv.innerHTML = '<div class="loading">Searching 480K+ clinical trials...</div>';

            try {
                const r = await fetch(`${BASE_URL}/v1/trials/search?${params}`, {
                    headers: { 'X-RapidAPI-Key': API_KEY }
                });

                if (r.status === 401) {
                    resultsDiv.innerHTML = '<div class="error">Invalid API key. Get your free key at <a href="https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api">rapidapi.com/clinical-trials-api</a></div>';
                    return;
                }

                if (!r.ok) {
                    resultsDiv.innerHTML = `<div class="error">API error: HTTP ${r.status}</div>`;
                    return;
                }

                const body = await r.json();
                if (!body.success) {
                    resultsDiv.innerHTML = `<div class="error">${body.error?.message || 'Unknown error'}</div>`;
                    return;
                }

                currentCursor = cursor;
                currentQuery = { query, phase, status };
                renderResults(body);

            } catch (e) {
                resultsDiv.innerHTML = `<div class="error">Network error: ${e.message}</div>`;
            }
        }

        function renderResults(body) {
            const resultsDiv = document.getElementById('results');
            const pagination = body.pagination;

            let html = `<p style="color: #666; margin-bottom: 15px;">Found <strong>${pagination.total.toLocaleString()}</strong> trials. Showing ${(currentCursor ? 'next page' : 'first')} ${body.data.length} results.</p>`;

            body.data.forEach(trial => {
                const loc = trial.locations && trial.locations[0] ? `${trial.locations[0].facility}, ${trial.locations[0].city}, ${trial.locations[0].state}` : '';
                html += `
                    <div class="trial-card">
                        <h3>${trial.title}</h3>
                        <div class="trial-meta">
                            <span class="nct">${trial.nct_id}</span>
                            <span class="badge badge-phase">${trial.phase}</span>
                            <span class="badge badge-recruiting">${trial.status}</span>
                            ${trial.enrollment_count ? `<span>${trial.enrollment_count} participants</span>` : ''}
                        </div>
                        <p class="summary">${(trial.brief_summary || '').substring(0, 200)}${(trial.brief_summary || '').length > 200 ? '...' : ''}</p>
                        ${loc ? `<p class="location">${loc}</p>` : ''}
                    </div>
                `;
            });

            html += '<div class="pagination">';

            if (currentCursor) {
                html += `<button onclick="searchTrials(null)">First Page</button>`;
            }

            if (pagination.has_more) {
                html += `<button onclick="loadNextPage('${pagination.next_cursor}')">Next Page (${pagination.total - (currentCursor ? parseInt(currentCursor.match(/\d+/)?.[0] || 0) : body.data.length)} remaining)</button>`;
            } else if (body.data.length > 0) {
                html += '<span style="color: #666;">All results loaded.</span>';
            }

            html += '</div>';
            resultsDiv.innerHTML = html;
        }

        function loadNextPage(cursor) {
            document.getElementById('query').value = currentQuery.query;
            document.getElementById('phase').value = currentQuery.phase;
            document.getElementById('status').value = currentQuery.status;
            searchTrials(cursor);
        }

        // Search on load
        searchTrials();
    </script>
</body>
</html>

전체 화면 모드로 전환 전체 화면 모드 종료

4단계: 실행하기

  1. 줄 ~70에서 YOUR_RAPIDAPI_KEY을 실제 API 키로 바꿔주세요
  2. 브라우저에서 index.html을 열어보세요 (파일을 두 번 클릭하세요)
  3. 작동하는 임상 시험 검색 앱을 가지고 있습니다

"알츠하이머", "당뇨병"을 검색해 보거나 기본값인 "유방암"을 남겨두세요 -- 실제 FDA 시험 데이터가 한 초 안에 표시됩니다.

다음은 무엇인가요?

  • 지리적 반경 검색 엔드포인트 (/v1/trials/nearby)를 추가하여 HTML5 지리적 위치 기반으로 "나에게 가까운 시험" 기능을 구축합니다
  • 구조화된 자격 요건 표시를 호출하여/v1/trials/{nct_id}/eligibility 사용자가 시험 카드를 클릭할 때 (프로 티어: $19/월)
  • POST /v1/alerts을 통해 새로운 시험 기준이 사용자의 저장된 검색 기준과 일치할 때 웹훅 알림을 설정하여 알림을 받으세요
  • Vercel, Netlify 또는 GitHub Pages에 배포하여 실시간 데모를 만드세요
  • Tailwind CSS로 스타일링하여 전문적인 모습으로 만드세요

API

RapidAPI에서 구독하세요 - 무료 티어, 신용카드가 필요 없습니다.


이 API로 무언가를 만들면 나를 태그해 주세요 - 만드신 것을 보고 싶어요. 댓글에 질문을 남기면 답변해 줄게요.