DEV Community

Rahul Ban
Rahul Ban

Posted on

How to Build a Clinical Trial Search App in 5 Minutes - Clinical Trials API Tutorial

What We're Building

In the next 5 minutes, you'll build a simple web app that lets users search for clinical trials by condition and phase, display results in cards, and paginate through results. Clinical Trials API is a REST API that wraps ClinicalTrials.gov into a developer-friendly interface. It gives you structured eligibility criteria (parsed into categories like Age, Gender, Condition, Lab Values -- no regex needed), geographic radius search by lat/lon, HTTP webhook alerts for new trials, cursor-based pagination, and aggregate statistics. Free tier available with no credit card. 480,000+ trials across 220+ countries, updated daily from the FDA. All powered by one API. You'll need basic HTML/JavaScript knowledge and a free RapidAPI account.

Step 1: Get Your API Key (30 Seconds)

  1. Go to https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api
  2. Click "Subscribe" on the Free plan (no credit card required)
  3. Copy your X-RapidAPI-Key from the dashboard

That's it. You're ready to make API calls.

Step 2: Make Your First API Call

Open your terminal and run this curl command (replace 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'
Enter fullscreen mode Exit fullscreen mode

You should see something like this:

{
  "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
  }
}
Enter fullscreen mode Exit fullscreen mode

That's 847 matching trials, returned in clean JSON with built-in pagination. No scraping, no parsing, no pipeline maintenance.

Step 3: Build the Trial Search App

Create a single HTML file (index.html) and paste this code:

<!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>
Enter fullscreen mode Exit fullscreen mode

Step 4: Run It

  1. Replace YOUR_RAPIDAPI_KEY on line ~70 with your actual API key
  2. Open index.html in your browser (just double-click the file)
  3. You have a working clinical trial search app

Try searching for "Alzheimer", "diabetes", or leave the default "breast cancer" -- you'll see real FDA trial data appear in under a second.

What's Next?

  • Add the geographic radius search endpoint (/v1/trials/nearby) to build a "trials near me" feature with HTML5 geolocation
  • Add structured eligibility display by calling /v1/trials/{nct_id}/eligibility when a user clicks a trial card (Pro tier: $19/mo)
  • Set up a webhook alert via POST /v1/alerts to get notified when new trials match your users' saved search criteria
  • Deploy to Vercel, Netlify, or GitHub Pages for a live demo
  • Style it with Tailwind CSS for a professional look

The API

Subscribe on RapidAPI - free tier, no credit card required.


If you build something with this API, tag me - I'd love to see what you create. Drop questions in the comments and I'll answer them.

Top comments (0)