Web Development

Build an AI Powered Resume Builder with React and OpenAI API (Full Guide)

Learn how to build an AI powered resume builder with React and OpenAI. Generate ATS friendly resumes, tailored suggestions, and export professional PDFs using OpenAI's API.

Build an AI Powered Resume Builder with React and OpenAI API (Full Guide)
AK
Ashish Kamat
Full Stack Developer & UI/UX Designer
November 20, 2025
8
424
#react#openai#chatgpt#AI#AI Development#Web Development#Resume Builder#JavaScript#API Integration

Stop Writing Resumes That Fail the ATS Test

Creating a strong resume is essential in today's competitive job market. Yet many people struggle with writing professional content that is both impactful and keyword optimized. Stop spending hours crafting bullet points that fail Applicant Tracking Systems (ATS). What if you could use artificial intelligence to craft high quality, ATS optimized resumes in minutes?

In this guide you will learn how to build a complete AI powered resume builder using React, the OpenAI API, and modern UI components. Your app will generate professional summaries, experience bullet points, and resume content tailored to target job descriptions.

Before we dive in, you may also want to check my recent post on the Next.js 16 release — Next.js 16 Released: Everything You Need to Know About the Fastest Version Yet.


Why Build an AI Powered Resume Builder?

Traditional resume builders give you templates but not content that converts. An AI resume builder provides:

  • Smart content suggestions using OpenAI
  • Keyword optimization to match job descriptions and ATS rules
  • ATS friendly formatting using action verbs and standard structure
  • Instant preview to see exactly how a resume will look
  • Speed to create a professional resume in minutes

This project benefits developers and job seekers. It also provides a strong example of practical prompt engineering and API integration.


Prerequisites

Make sure you have:

  • Basic React and JavaScript knowledge
  • Node.js and npm installed
  • An OpenAI API key
  • A code editor such as VS Code

Project Setup

Create the React app and install dependencies.

bash
npx create-react-app ai-resume-builder
cd ai-resume-builder
npm install axios lucide-react

We will use:

  • Axios for API calls
  • Lucide React for icons
  • React state to manage resume data

App Architecture Overview

The app has three main sections:

  1. Resume Form for user input
  2. Resume Preview for live rendering and export
  3. AI Suggestions for content generation and optimization

Each section is a separate component. State is lifted to the App component and passed down.


1. Main App Component

jsx
// src/App.js
import React, { useState } from 'react';
import ResumeForm from './components/ResumeForm';
import ResumePreview from './components/ResumePreview';
import AISuggestions from './components/AISuggestions';
import './App.css';

function App() {
  const [resumeData, setResumeData] = useState({
    personalInfo: {
      name: '',
      email: '',
      phone: '',
      location: '',
      linkedin: '',
      portfolio: ''
    },
    summary: '',
    experience: [],
    education: [],
    skills: [],
    customSections: []
  });

  const [activeTab, setActiveTab] = useState('form');

  return (
    <div className="app">
      <header className="app-header">
        <h1>AI Resume Builder</h1>
        <p>Create professional resumes with AI powered suggestions</p>
      </header>

      <nav className="tab-navigation">
        <button className={activeTab === 'form' ? 'active' : ''} onClick={() => setActiveTab('form')}>
          Build Resume
        </button>
        <button className={activeTab === 'preview' ? 'active' : ''} onClick={() => setActiveTab('preview')}>
          Preview
        </button>
        <button className={activeTab === 'ai' ? 'active' : ''} onClick={() => setActiveTab('ai')}>
          AI Suggestions
        </button>
      </nav>

      <main>
        {activeTab === 'form' && <ResumeForm resumeData={resumeData} setResumeData={setResumeData} />}
        {activeTab === 'preview' && <ResumePreview resumeData={resumeData} />}
        {activeTab === 'ai' && <AISuggestions resumeData={resumeData} setResumeData={setResumeData} />}
      </main>
    </div>
  );
}

export default App;

2. Resume Form Component

This component groups smaller form sections. Each section updates the corresponding slice of resumeData.

jsx
// src/components/ResumeForm.js
import React from 'react';
import PersonalInfo from './PersonalInfo';
import Experience from './Experience';
import Education from './Education';
import Skills from './Skills';

const ResumeForm = ({ resumeData, setResumeData }) => {
  const updateSection = (section, data) => {
    setResumeData(prev => ({ ...prev, [section]: data }));
  };

  return (
    <div className="resume-form">
      <PersonalInfo data={resumeData.personalInfo} onChange={data => updateSection('personalInfo', data)} />
      <Experience data={resumeData.experience} onChange={data => updateSection('experience', data)} />
      <Education data={resumeData.education} onChange={data => updateSection('education', data)} />
      <Skills data={resumeData.skills} onChange={data => updateSection('skills', data)} />
    </div>
  );
};

export default ResumeForm;

3. AI Suggestions Component

This component sends the resume state and optional job description to OpenAI and displays suggestions. The suggestions can be applied with one click.

jsx
// src/components/AISuggestions.js
import React, { useState } from 'react';
import { Sparkles, Loader } from 'lucide-react';
import { generateAISuggestions } from '../services/openaiService';

const AISuggestions = ({ resumeData, setResumeData }) => {
  const [suggestions, setSuggestions] = useState({});
  const [loading, setLoading] = useState(false);
  const [jobDescription, setJobDescription] = useState('');

  const handleGetSuggestions = async () => {
    setLoading(true);
    try {
      const aiSuggestions = await generateAISuggestions(resumeData, jobDescription);
      setSuggestions(aiSuggestions);
    } catch (error) {
      console.error('AI suggestions error:', error);
      alert('Error getting suggestions. Please try again.');
    }
    setLoading(false);
  };

  const applySuggestion = (section, index, newContent) => {
    const updated = { ...resumeData };
    if (section === 'experience' && updated.experience[index]) {
      updated.experience[index].description = newContent;
      setResumeData(updated);
    }
    if (section === 'summary') {
      updated.summary = newContent;
      setResumeData(updated);
    }
    if (section === 'skills') {
      updated.skills = newContent.split(',').map(s => s.trim()).filter(Boolean);
      setResumeData(updated);
    }
  };

  return (
    <div className="ai-suggestions">
      <div className="suggestion-header">
        <Sparkles size={20} />
        <h2>AI Powered Resume Suggestions</h2>
      </div>

      <textarea
        className="job-description-input"
        value={jobDescription}
        onChange={e => setJobDescription(e.target.value)}
        placeholder="Paste job description for tailored recommendations..."
        rows={6}
      />

      <div style={{ marginTop: 12 }}>
        <button onClick={handleGetSuggestions} disabled={loading} className="suggest-button">
          {loading ? <Loader className="spin" /> : 'Get AI Suggestions'}
        </button>
      </div>

      {suggestions.summary && (
        <section className="suggestions-block">
          <h3>Professional Summary Suggestion</h3>
          <p>{suggestions.summary}</p>
          <button onClick={() => applySuggestion('summary', null, suggestions.summary)}>Apply Summary</button>
        </section>
      )}

      {suggestions.experience && suggestions.experience.length > 0 && (
        <section className="suggestions-block">
          <h3>Experience Suggestions</h3>
          {suggestions.experience.map((s, i) => (
            <div key={i} className="suggestion-item">
              <p><strong>Current:</strong> {resumeData.experience[i]?.description || 'No current description'}</p>
              <p><strong>AI Suggestion:</strong> {s}</p>
              <button onClick={() => applySuggestion('experience', i, s)}>Apply Suggestion</button>
            </div>
          ))}
        </section>
      )}

      {suggestions.skills && (
        <section className="suggestions-block">
          <h3>Skills Suggestion</h3>
          <p>{suggestions.skills}</p>
          <button onClick={() => applySuggestion('skills', null, suggestions.skills)}>Apply Skills</button>
        </section>
      )}
    </div>
  );
};

export default AISuggestions;

Integrating OpenAI API and Prompt Engineering

This service handles prompt creation, safe parsing, and the API call. The prompt is crafted to request a JSON response, which makes parsing reliable.

jsx
// src/services/openaiService.js
import axios from 'axios';

const OPENAI_API_KEY = process.env.REACT_APP_OPENAI_API_KEY;
const OPENAI_URL = 'https://api.openai.com/v1/chat/completions';

const client = axios.create({
  baseURL: OPENAI_URL,
  headers: {
    Authorization: `Bearer ${OPENAI_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

const createSuggestionPrompt = (resumeData, jobDescription) => {
  const experienceText = resumeData.experience.map(exp =>
    `- ${exp.title} at ${exp.company} (${exp.startDate || 'N/A'} - ${exp.endDate || 'Present'}): ${exp.description || ''}`
  ).join('\n');

  return `
You are a professional resume writer and career coach focusing on ATS compliance.
Analyze the resume data below and provide specific, actionable suggestions.
Optimize experience descriptions to use strong action verbs, measurable results, and relevant keywords.

RESUME DATA:
PERSONAL INFO: ${resumeData.personalInfo.name || 'N/A'}
SUMMARY: ${resumeData.summary || 'N/A'}
EXPERIENCE:
${experienceText}

TARGET JOB DESCRIPTION:
${jobDescription || 'N/A'}

Respond only as a JSON object with this structure:
{
  "summary": "optimized professional summary",
  "experience": ["suggestion for exp 1", "suggestion for exp 2", ...],
  "skills": "comma separated skills suggestion"
}
`;
};

const parseAISuggestions = (aiResponse) => {
  try {
    const jsonMatch = aiResponse.match(/\{[\s\S]*\}/);
    if (jsonMatch) {
      return JSON.parse(jsonMatch[0]);
    }
    throw new Error('No JSON found in response');
  } catch (error) {
    console.error('Error parsing AI suggestions:', error);
    return { experience: [], summary: '', skills: '' };
  }
};

export const generateAISuggestions = async (resumeData, jobDescription = '') => {
  const prompt = createSuggestionPrompt(resumeData, jobDescription);

  try {
    const response = await client.post('', {
      model: 'gpt-4o-mini', // replace with your preferred model or keep gpt-3.5-turbo
      messages: [
        { role: 'system', content: 'You are a professional resume writer.' },
        { role: 'user', content: prompt }
      ],
      max_tokens: 1000,
      temperature: 0.6
    });

    const content = response.data.choices?.[0]?.message?.content || '';
    return parseAISuggestions(content);
  } catch (error) {
    console.error('OpenAI API error:', error?.response?.data || error.message);
    throw new Error('Failed to generate suggestions');
  }
};

Key prompt engineering tips

  1. Ask for a JSON only response to make parsing predictable.
  2. Request quantifiable examples and action verbs.
  3. Provide the target job description for tailored suggestions.
  4. Set temperature low for consistent output.

Resume Preview and Export

Use a simple export utility to convert the preview to PDF. Keep the preview minimal and ATS friendly. Here is a simple preview component snippet.

jsx
// src/components/ResumePreview.js
import React from 'react';
import { Download } from 'lucide-react';
import { exportToPDF } from '../utils/exportUtils';

const ResumePreview = ({ resumeData }) => {
  const handleExport = () => exportToPDF(resumeData);

  return (
    <div className="resume-preview">
      <div className="preview-controls">
        <button onClick={handleExport} className="export-button">
          <Download size={16} /> Export as PDF
        </button>
      </div>

      <div className="resume-document">
        <header className="resume-header">
          <h1>{resumeData.personalInfo.name || 'Your Name'}</h1>
          <div className="contact-info">
            <span>{resumeData.personalInfo.email}</span>
            <span>{resumeData.personalInfo.phone}</span>
            <span>{resumeData.personalInfo.location}</span>
          </div>
        </header>

        {resumeData.summary && (
          <section>
            <h2>Professional Summary</h2>
            <p>{resumeData.summary}</p>
          </section>
        )}

        {resumeData.experience?.length > 0 && (
          <section>
            <h2>Professional Experience</h2>
            {resumeData.experience.map((exp, i) => (
              <div key={i}>
                <h3>{exp.title} — {exp.company}</h3>
                <div>{exp.startDate} - {exp.endDate || 'Present'}</div>
                <p>{exp.description}</p>
              </div>
            ))}
          </section>
        )}

        {resumeData.skills?.length > 0 && (
          <section>
            <h2>Skills</h2>
            <div>{resumeData.skills.join(', ')}</div>
          </section>
        )}
      </div>
    </div>
  );
};

export default ResumePreview;

Styling suggestions

Use clean, readable fonts and simple layout. Keep styles minimal for ATS compatibility. Example CSS highlights:

css
/* src/App.css */
:root {
  --primary: #2b6cb0;
  --muted: #6b7280;
}

body {
  font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
  color: #111827;
  background: #f8fafc;
}

.app { max-width: 1100px; margin: 0 auto; padding: 28px; }
.resume-document { background: #ffffff; padding: 32px; border-radius: 8px; box-shadow: 0 6px 18px rgba(17,24,39,0.06); }
.resume-header h1 { margin: 0; font-size: 22px; color: var(--primary); }
.resume-section h2 { color: var(--primary); }
.suggest-button { background: var(--primary); color: white; padding: 10px 14px; border-radius: 6px; border: none; cursor: pointer; }

Advanced features to add

Futuristic dashboard with charts

Futuristic dashboard with charts

Enhance the app with:

  • ATS score checker with a visual score
  • Multiple resume templates for different industries
  • Cover letter generator paired with the resume
  • User accounts and saved resumes in cloud storage
  • Analytics for resume keyword coverage and density

Conclusion

Celebration success at work

Celebration success at work

You built an AI powered resume builder using React and OpenAI. This project shows how to combine prompt engineering, API integration, and a clear user interface to solve a practical problem.

Key takeaways

  • Use structured prompts and ask for JSON responses for reliable parsing.
  • Keep resumes ATS friendly by using standard sections, simple formatting, and action verbs.
  • Provide a preview and an export option so users can get a ready to use PDF.

Next steps

  1. Refine the prompts for different industries and seniority levels.
  2. Add templates and an ATS scoring interface.
  3. Support user accounts and resume versioning.
  4. Share your best prompts and results with the community.

This project is for educational purposes. Always comply with OpenAI usage policies and protect user data.

Happy coding! 🚀

Article Summary

Learn how to build an AI powered resume builder with React and OpenAI. Generate ATS friendly resumes, tailored suggestions, and export professional PDFs using OpenAI's API.

Share Article
Article Info
Published November 20, 2025
Web Development
8

Tags

reactopenaichatgptAIAI DevelopmentWeb DevelopmentResume BuilderJavaScriptAPI Integration
About the Author
AK

Ashish Kamat

Full Stack Developer & UI/UX Designer

View Profile
Stay Updated

Get the latest articles and tutorials delivered to your inbox.

Subscribe Now