Looking for a hands-on project to sharpen your Python and AI skills? The best way to learn is by building, and in this tutorial, we’ll create a practical AI application from scratch. We’ll connect to live APIs, build a simple web server, and use a Large Language Model to create a personalized news summarizer.
In this project-based tutorial, we’re going to do exactly that. We’ll build a simple but powerful web application that fetches the latest news on any topic you choose and uses the power of AI to give you a quick, easy-to-read summary.
This is a perfect project for your portfolio. It demonstrates real-world skills in Python, web development, and one of the hottest topics in tech: Retrieval-Augmented Generation (RAG). Let’s get started!
What We’re Building
Here’s the game plan:
- A user types a topic (like “electric cars” or “space exploration”) into a web page.
- Our Python backend springs into action, fetching the latest news articles on that topic from a live News API.
- For each article, we’ll send its content to a Large Language Model (LLM)—in this case, Google’s Gemini—and ask it to generate a concise summary.
- The summaries are then sent back to the web page for the user to read.
The Tech Stack (What You’ll Learn)
- Python: The core language for our backend logic.
- Flask: A beginner-friendly Python framework for building the web server.
- APIs: We’ll learn how to request data from two different APIs: NewsAPI (for articles) and the Google AI API (for summarization).
- HTML & JavaScript: For the simple, functional frontend.
- Virtual Environments: The professional way to manage project dependencies.
Phase 1: Setting Up Your Digital Workspace
First things first, let’s get our project folder and tools ready.
- Create a Project Folder: On your Desktop, create a new folder called
NewsSummarizer
. - Open Your Terminal: We’ll be using the command line to set things up.
- Create a Virtual Environment: This is a crucial best practice. It creates an isolated “sandbox” for our project’s libraries. Navigate into your project folder and run the following commands:
# Navigate into your new folder cd ~/Desktop/NewsSummarizer # Create the virtual environment python3 -m venv venv # Activate it (you'll need to do this every time you work on the project) source venv/bin/activate
You’ll know it’s active when you see(venv)
at the start of your terminal prompt. - Install the Libraries: With your environment active, run this single command to install all the Python libraries we need:
pip install Flask requests google-generativeai python-dotenv
Phase 2: Getting Your API Keys
API keys are like secret passwords that let our application access services. We need two free ones.
- NewsAPI Key: Go to newsapi.org, click “Get API Key,” and sign up. You’ll find the key on your dashboard.
- Google AI (Gemini) Key: Go to aistudio.google.com. Click “Get API key” and then “Create API key in new project.”
Once you have both keys, create a file in your NewsSummarizer
folder named .env
. The leading dot is important! Inside this file, paste your keys like so:
NEWS_API_KEY=YOUR_NEWS_API_KEY_HERE
GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY_HERE
Phase 3: Building the Brains (The Python Backend)
This is where the main logic lives. Create a file named app.py
in your project folder and add the following code. It’s fully commented to explain every step.
# --- Import Necessary Libraries ---
import os # Used to access environment variables (our API keys)
import requests # Used to make HTTP requests to the News API
import google.generativeai as genai # The Google AI library for Gemini
from flask import Flask, request, jsonify, render_template # The main components from Flask for building the web app
from dotenv import load_dotenv # Used to load the environment variables from our .env file
# --- Initial Setup ---
# Load the environment variables from the .env file
load_dotenv()
# Create a Flask application instance
# The 'static_folder' and 'template_folder' tell Flask where to find our CSS/JS and HTML files
app = Flask(__name__, static_folder='static', template_folder='.')
# --- API Configuration ---
# Configure the Google AI client with the API key from our .env file
# This line sets up the connection to the Gemini model
try:
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
# Initialize the generative model we want to use
# *** FIX: Updated model name to a more current and available version ***
model = genai.GenerativeModel('gemini-1.5-flash-latest')
except Exception as e:
print(f"Error configuring Google AI: {e}")
# You might want to handle this more gracefully in a real app
model = None
# Get the News API key from our .env file
NEWS_API_KEY = os.environ.get("NEWS_API_KEY")
# The base URL for the News API's 'everything' endpoint
NEWS_API_URL = "https://newsapi.org/v2/everything"
# --- Main Application Routes ---
# This defines the main (or "root") page of our web app
@app.route('/')
def index():
"""
This function runs when a user visits the main URL (e.g., http://127.0.0.1:5000)
It tells Flask to find the 'index.html' file and send it to the user's browser.
"""
return render_template('index.html')
# This defines the '/summarize' endpoint that our frontend will call
@app.route('/summarize', methods=['POST'])
def summarize():
"""
This function handles the main logic: fetching and summarizing news.
It's triggered when the frontend sends a POST request to '/summarize'.
"""
# Check if the API keys are available
if not NEWS_API_KEY or not model:
return jsonify({'error': 'API key configuration is missing. Please check your .env file.'}), 500
# --- 1. Get Keywords from Frontend ---
# Get the JSON data sent from the frontend JavaScript
data = request.get_json()
# Extract the 'keywords' from the data
keywords = data.get('keywords')
# If no keywords were provided, return an error
if not keywords:
return jsonify({'error': 'No keywords provided.'}), 400
# --- 2. Retrieve Articles (The "R" in RAG) ---
try:
# Set up the parameters for our News API request
params = {
'q': keywords, # The search keywords
'apiKey': NEWS_API_KEY, # Your API key
'language': 'en', # We want English articles
'pageSize': 5, # Let's get the top 5 articles
'sortBy': 'relevancy' # Sort by the most relevant first
}
# Make the request to the News API
response = requests.get(NEWS_API_URL, params=params)
# Raise an exception if the request failed (e.g., 404, 500)
response.raise_for_status()
# Parse the JSON response from the API
news_data = response.json()
articles = news_data.get('articles', [])
# If no articles were found, return a specific message
if not articles:
return jsonify({'error': f"No articles found for '{keywords}'. Try a different topic."}), 404
except requests.exceptions.RequestException as e:
# Handle network errors or bad responses from the News API
return jsonify({'error': f'Could not fetch news: {e}'}), 500
# --- 3. Generate Summaries (The "G" in RAG) ---
summaries = []
for article in articles:
# We only care about articles with some content
if not article.get('content'):
continue
# Create a detailed prompt for the AI
# This is where we "augment" the AI's knowledge with the retrieved article
prompt = f"""
Please provide a concise, one-paragraph summary of the following news article.
Focus on the key takeaways and the most important information.
ARTICLE TITLE: {article['title']}
ARTICLE CONTENT: {article['content']}
SUMMARY:
"""
try:
# Send the prompt to the Gemini model
ai_response = model.generate_content(prompt)
# Extract the text from the AI's response
summary_text = ai_response.text
except Exception as e:
# If the AI fails for one article, we can just skip it
print(f"Could not generate summary for '{article['title']}': {e}")
summary_text = "Summary could not be generated for this article."
# Add the processed article to our list of summaries
summaries.append({
'title': article['title'],
'source': article['source']['name'],
'summary': summary_text,
'url': article['url']
})
# --- 4. Send Results to Frontend ---
# Return the list of summaries as a JSON response
return jsonify({'summaries': summaries})
# --- Run the Application ---
# This is a standard Python practice to make sure the server only runs
# when the script is executed directly (not when imported as a module)
if __name__ == '__main__':
# Starts the Flask development server
# debug=True allows you to see errors in the browser and automatically reloads the server when you save changes
app.run(debug=True)
Phase 4: Creating the Face (The Frontend)
Now for the user interface. Create a file named index.html
and add this code. It’s simple HTML with some JavaScript to talk to our Python backend.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple News Summarizer</title>
<style>
/* A little bit of basic styling to make it readable */
body {
font-family: sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 0 20px;
background-color: #f9f9f9;
}
input {
width: 70%;
padding: 8px;
font-size: 16px;
}
button {
padding: 8px 12px;
font-size: 16px;
cursor: pointer;
}
#results {
margin-top: 20px;
}
.article {
background-color: #fff;
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
}
h2 {
margin-top: 0;
}
</style>
</head>
<body>
<h1>Personalized News Summarizer</h1>
<p>Enter a topic to get AI-powered summaries of the latest news.</p>
<!-- Input section -->
<input type="text" id="keywords" placeholder="e.g., 'electric vehicles'">
<button id="summarize-btn">Get Summaries</button>
<!-- Loading/Error message area -->
<p id="status"></p>
<!-- Results will be displayed here -->
<div id="results"></div>
<script>
// --- Get references to the HTML elements we need ---
const keywordsInput = document.getElementById('keywords');
const summarizeBtn = document.getElementById('summarize-btn');
const resultsDiv = document.getElementById('results');
const statusP = document.getElementById('status');
// --- Listen for a click on the button ---
summarizeBtn.addEventListener('click', getSummaries);
// --- Listen for the "Enter" key in the input box ---
keywordsInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
getSummaries();
}
});
// --- Main function to fetch and display summaries ---
async function getSummaries() {
// 1. Get the user's keywords from the input box
const keywords = keywordsInput.value;
// Simple validation
if (!keywords) {
statusP.textContent = "Please enter a topic.";
return;
}
// 2. Clear previous results and show a loading message
resultsDiv.innerHTML = '';
statusP.textContent = 'Fetching and summarizing news...';
try {
// 3. Send the keywords to our Python backend
// This is the core of the frontend-backend communication.
const response = await fetch('/summarize', {
method: 'POST', // We are sending data, so we use POST
headers: {
'Content-Type': 'application/json' // Tell the server we're sending JSON
},
body: JSON.stringify({ keywords: keywords }) // Convert our JS object to a JSON string
});
// 4. Get the response from the backend
const data = await response.json();
// 5. Check if the backend sent an error
if (data.error) {
statusP.textContent = `Error: ${data.error}`;
} else {
// If successful, display the summaries
statusP.textContent = ''; // Clear the status message
displaySummaries(data.summaries);
}
} catch (error) {
// This catches network errors (e.g., if the server is down)
statusP.textContent = 'An error occurred. Make sure the Python server is running.';
console.error("Fetch error:", error);
}
}
// --- Helper function to display the articles ---
function displaySummaries(summaries) {
// If the backend found no articles
if (summaries.length === 0) {
statusP.textContent = "No articles found for that topic.";
return;
}
// Loop through each summary and create HTML for it
summaries.forEach(article => {
// Create a new div for the article
const articleDiv = document.createElement('div');
articleDiv.className = 'article'; // Add a class for styling
// Use innerHTML to create the content for the article card
articleDiv.innerHTML = `
<h2>${article.title}</h2>
<p><strong>Source:</strong> ${article.source}</p>
<p>${article.summary}</p>
<a href="${article.url}" target="_blank">Read Full Article</a>
`;
// Add the new article card to the results area
resultsDiv.appendChild(articleDiv);
});
}
</script>
</body>
</html>
Phase 5: Bring It to Life!
You’re all set! Go to your terminal (make sure you’re in the NewsSummarizer
folder and your virtual environment is active) and run the server:
python3 app.py
Open your web browser and go to http://127.0.0.1:5000
. Your app should be running!
Phase 6: Share Your Work on GitHub
Now it’s time to show off your project. Create a new, empty repository on GitHub. Then, you can upload your files manually.
IMPORTANT: When you upload, only add app.py
and index.html
. NEVER make your .env
file or your venv
folder public.
Final Thoughts
Congratulations! You’ve just built a full-stack web application that uses a powerful AI model. This project covers so many essential skills for modern developers.
From here, you could try adding new features:
- Improve the UI with a framework like Tailwind CSS.
- Let the user select the number of articles to summarize.
- Add a “Copy Summary” button.
Feel free to grab the code from my GitHub repository here to check your work: [Link to my GitHub repository]
Happy coding!








