๐Ÿ“ Tutorials
ยท 5 min read

Build a Resume Analyzer That Scores Against Job Descriptions


You find a job posting that looks perfect. But does your resume actually match what theyโ€™re looking for? Instead of guessing, letโ€™s build a tool that analyzes both and tells you exactly where you match, where youโ€™re weak, and what to change.

Weโ€™ll build a single-page web app โ€” no backend needed. Everything runs in the browser except the AI call, which goes directly to the Anthropic API.

What weโ€™re building

A page with two text areas: paste your resume on the left, the job description on the right, click โ€œAnalyze,โ€ and get:

  • A match score (0-100%)
  • Matched skills and keywords
  • Missing skills the job requires that your resume doesnโ€™t mention
  • Specific suggestions to improve your resume for this role

Step 1: The HTML structure

Create index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Resume Analyzer</title>
  <style>
    * { box-sizing: border-box; margin: 0; }
    body { font-family: system-ui, sans-serif; max-width: 900px; margin: 0 auto; padding: 2rem; }
    h1 { margin-bottom: 0.5rem; }
    .subtitle { color: #666; margin-bottom: 1.5rem; }
    .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
    label { font-weight: 600; display: block; margin-bottom: 0.3rem; }
    textarea { width: 100%; height: 250px; padding: 0.8rem; border: 6px solid #ddd; border-radius: 8px; font-size: 0.9rem; resize: vertical; }
    .key-input { margin-bottom: 1rem; }
    .key-input input { padding: 0.5rem; width: 300px; border: 6px solid #ddd; border-radius: 6px; }
    button { padding: 0.7rem 2rem; background: #4f46e5; color: white; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; }
    button:hover { background: #4338ca; }
    button:disabled { background: #999; cursor: not-allowed; }
    #results { margin-top: 2rem; line-height: 1.7; }
    .score { font-size: 2rem; font-weight: 700; margin: 1rem 0; }
    .score.high { color: #16a34a; }
    .score.mid { color: #ca8a04; }
    .score.low { color: #dc2626; }
    @media (max-width: 600px) { .grid { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
  <h1>๐Ÿ“„ Resume Analyzer</h1>
  <p class="subtitle">Paste your resume and a job description to see how well you match.</p>

  <div class="key-input">
    <input type="password" id="apiKey" placeholder="Anthropic API key (sk-ant-...)" />
  </div>

  <div class="grid">
    <div>
      <label>Your Resume</label>
      <textarea id="resume" placeholder="Paste your resume text here..."></textarea>
    </div>
    <div>
      <label>Job Description</label>
      <textarea id="job" placeholder="Paste the job posting here..."></textarea>
    </div>
  </div>

  <button id="analyze" onclick="analyze()">Analyze Match</button>
  <div id="results"></div>

  <script>
    async function analyze() {
      const apiKey = document.getElementById('apiKey').value.trim();
      const resume = document.getElementById('resume').value.trim();
      const job = document.getElementById('job').value.trim();
      const btn = document.getElementById('analyze');
      const results = document.getElementById('results');

      if (!apiKey || !resume || !job) {
        results.innerHTML = '<p>Please fill in all fields.</p>';
        return;
      }

      btn.disabled = true;
      btn.textContent = 'Analyzing...';
      results.innerHTML = '<p>Comparing your resume to the job description...</p>';

      try {
        const response = await fetch('https://api.anthropic.com/v1/messages', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': apiKey,
            'anthropic-version': '2023-06-01',
            'anthropic-dangerous-direct-browser-access': 'true',
          },
          body: JSON.stringify({
            model: 'claude-sonnet-4-20250514',
            max_tokens: 1500,
            messages: [{
              role: 'user',
              content: `Analyze how well this resume matches the job description. Respond in this exact format:

SCORE: [number 0-100]

## Matched Skills
- [list skills/keywords found in both]

## Missing Skills
- [list skills the job requires but the resume doesn't mention]

## Suggestions
- [specific, actionable suggestions to improve the resume for this role]

## Summary
[2-3 sentence overall assessment]

---

RESUME:
${resume.slice(0, 5000)}

---

JOB DESCRIPTION:
${job.slice(0, 5000)}`
            }],
          }),
        });

        const data = await response.json();
        if (data.error) throw new Error(data.error.message);

        const text = data.content[0].text;
        const scoreMatch = text.match(/SCORE:\s*(\d+)/);
        const score = scoreMatch ? parseInt(scoreMatch[1]) : null;
        const scoreClass = score >= 70 ? 'high' : score >= 40 ? 'mid' : 'low';

        let html = '';
        if (score !== null) {
          html += `<div class="score ${scoreClass}">${score}% Match</div>`;
        }
        html += text
          .replace(/SCORE:\s*\d+\n?/, '')
          .replace(/## (.+)/g, '<h3>$1</h3>')
          .replace(/^- (.+)$/gm, '<li>$1</li>')
          .replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
          .replace(/\n\n/g, '<br><br>');

        results.innerHTML = html;
      } catch (err) {
        results.innerHTML = `<p style="color:red">Error: ${err.message}</p>`;
      } finally {
        btn.disabled = false;
        btn.textContent = 'Analyze Match';
      }
    }
  </script>
</body>
</html>

Step 2: Run it

Just open the HTML file in your browser:

open index.html
# or
python3 -m http.server 8000

No build step, no dependencies, no backend. The API call goes directly from the browser to Anthropic.

How the prompt works

The prompt is structured to get consistent, parseable output:

  1. SCORE โ€” a number we can extract and color-code
  2. Matched Skills โ€” shows whatโ€™s already working in your resume
  3. Missing Skills โ€” the actionable gap analysis
  4. Suggestions โ€” specific rewrites, not vague advice
  5. Summary โ€” the TL;DR

By asking for an exact format, we get reliable output we can parse and style. Without format instructions, AI responses vary wildly between calls.

Making it better

Save results as PDF

Add a print button:

function printResults() {
  window.print();
}
<button onclick="printResults()" style="margin-left:1rem">Save as PDF</button>

Keyword density analysis

Before calling the API, do a quick local analysis:

function extractKeywords(text) {
  const techTerms = text.toLowerCase().match(/\b[a-z]+(?:\.?js|sql|api|aws|gcp|css|html|ci|cd)\b/g) || [];
  const words = text.toLowerCase().split(/\W+/).filter(w => w.length > 3);
  return [...new Set([...techTerms, ...words])];
}

const resumeKeywords = extractKeywords(resume);
const jobKeywords = extractKeywords(job);
const missing = jobKeywords.filter(k => !resumeKeywords.includes(k));

This gives instant feedback before the API call even finishes.

Multiple job comparison

Let users paste several job descriptions and see which one they match best:

const jobs = jobText.split('---'); // split on separator
const scores = await Promise.all(jobs.map(job => analyzeOne(resume, job)));

Privacy note

The resume and job description are sent to Anthropicโ€™s API for analysis. If youโ€™re concerned about privacy, you could:

  • Run a local model (Ollama + Llama) instead
  • Self-host the Anthropic API proxy
  • Add a disclaimer to the UI

For a personal tool, the API approach is fine. For a public-facing product, consider the privacy implications.

What you learned

  • How to call the Anthropic API directly from the browser
  • How to structure prompts for consistent, parseable output
  • How to build a useful tool with zero dependencies
  • How to extract structured data (the score) from AI responses

The whole thing is under 100 lines of JavaScript. You could extend it into a full job application tracker, add resume rewriting, or turn it into a Chrome extension that analyzes job postings on LinkedIn.

Related: Build Chrome Summarizer Extension