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.

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.
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:
- Resume Form for user input
- Resume Preview for live rendering and export
- 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
// 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.
// 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.
// 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.
// 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
- Ask for a JSON only response to make parsing predictable.
- Request quantifiable examples and action verbs.
- Provide the target job description for tailored suggestions.
- 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.
// 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:
/* 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
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
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
- Refine the prompts for different industries and seniority levels.
- Add templates and an ATS scoring interface.
- Support user accounts and resume versioning.
- 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.
Tags
Get the latest articles and tutorials delivered to your inbox.
Subscribe Now