Build a Polyglot Content Analyzer
Build a REST API in Python consumed by JavaScript and Java clients.
Introduction
30 Second Summary
You write every day. Emails to your team, LinkedIn posts, cover letter paragraphs, Slack messages to your manager. But you never get instant feedback on whether your tone sounds negative, your sentences are too complex, or your writing reads at a college-textbook level.
In this project, you will build a Smart Content Analyzer that takes any English text and instantly returns a professional writing report. You will build the analysis engine in Python, a visual web interface in JavaScript, and a command-line client in Java to prove the whole system is language-agnostic.
What You'll Build
You paste a paragraph into your analyzer, click one button, and immediately see colour-coded scores telling you exactly how your writing lands.
By the end of this project, you'll have:
- A web interface where you paste text and instantly see colour-coded sentiment (green for positive, red for negative), reading ease scores, grade level, and word complexity.
- A Java command-line client that you run from the terminal, type or paste text into, and get the same structured analysis printed back.
- Proof that REST APIs are language-agnostic. Both clients talk to the exact same Python API, showing you can build one service and consume it from anywhere.
- Secret Mission: Add a writing suggestions endpoint that checks your scores against thresholds and returns actionable tips to improve your writing.
Are there any prerequisites?
You need Python 3.10 or newer, JDK 11 or newer, and a code editor installed on your machine. Basic familiarity with Python, JavaScript, and Java syntax is helpful but not required.
Before We Start
Before we start building, take a moment to commit to what you're about to create and why it matters. Understanding the goal upfront makes every step that follows click into place.
Set Up Your Python Environment
Python on its own has no web server and no text analysis capability. To turn it into an NLP-powered API, you need Flask (a lightweight web framework), TextBlob (for sentiment analysis), and textstat (for readability scoring).
In this step, you will create an isolated project folder, install all four libraries, and confirm everything is wired up correctly before writing any application code.
In this step, get ready to:
- Create a project folder and a Python virtual environment.
- Install Flask, TextBlob, textstat, and flask-cors.
- Download TextBlob corpora and verify all imports work.
Create a project folder and set up a Python virtual environment
Before installing any packages, you need to confirm you have Python 3.10 or higher. TextBlob 0.20.0 requires Python 3.10 as a minimum.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.10 or higher
You are good to go. Continue to the next instruction below.
ⓧ I see an older version
Your system has an older Python that will not work with TextBlob 0.20.0. You need to install Python 3.10 or newer alongside it.
- On macOS, download and install the latest Python 3 from python.org. The installer places the new Python at /usr/local/bin/python3 and does not overwrite the system version.
- On Windows, download the Python 3.10+ installer from python.org. Check the Add Python to PATH box before clicking Install Now.
- After installing, close and reopen your terminal, then re-run the version check above.
ⓧ Command not found
Python is not installed on your machine yet.
- On macOS, download and install the latest Python 3 from python.org. The installer adds Python to your PATH automatically.
- On Windows, download the Python 3.10+ installer from python.org. Check the Add Python to PATH box before clicking Install Now.
- After installing, close and reopen your terminal, then run the version check again.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir content-analyzer
cd content-analyzer
- Create a virtual environment by running this command:
macOS
python3 -m venv venv
Windows
python -m venv venv
What is a virtual environment?
A virtual environment is an isolated copy of Python that lives inside your project folder. Packages you install here stay local to this project and do not interfere with other Python work on your machine.
- Activate the virtual environment by running this command:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
- On Windows, if you see a permissions error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned in PowerShell first, then try the activate command again.
- Make sure you are inside the content-analyzer folder before activating. Run ls (macOS) or dir (Windows) and confirm you see a venv folder listed.
- help me activate my Python virtual environment
Install Flask, TextBlob, textstat, and flask-cors via pip
With the virtual environment active, every package you install stays isolated inside content-analyzer/venv.
- Install all four libraries at once by running this command:
pip install Flask textblob textstat flask-cors
You will see pip downloading and installing each package along with its dependencies. The output ends with a "Successfully installed" line listing everything that was added.
What are these packages?
- Flask is the web framework that turns your Python script into an API server.
- textblob provides one-line sentiment analysis (polarity and subjectivity scores).
- textstat calculates readability metrics like Flesch Reading Ease and grade level.
- flask-cors allows browsers to call your API without being blocked by CORS security rules.
Seeing a permission error or install failure?
- Confirm (venv) appears in your prompt. If it does not, re-run the activate command from the previous substep.
- If pip itself is outdated, run pip install --upgrade pip and then re-run the install command.
- help me fix my pip install error
Download TextBlob corpora and verify all imports work
TextBlob depends on language data (called corpora) that is not included in the pip package. You need to download it separately before sentiment analysis will function.
- Download the TextBlob corpora by running this command:
python -m textblob.download_corpora
You will see a series of downloads (tokenizers, taggers, corpora). The output finishes with "Finished" once all data is in place.
- Before you run the next commands, what do you expect to see if Flask installed correctly?
- Verify Flask is installed by running this command:
python -m flask --version
You should see Flask 3.1.3 printed (along with Python and Werkzeug versions).
- Now verify TextBlob works by running this command:
python -c "from textblob import TextBlob; print(TextBlob('hello').sentiment)"
You should see a Sentiment namedtuple printed, something like Sentiment(polarity=0.0, subjectivity=0.0). This confirms TextBlob can analyze text.
Seeing a ModuleNotFoundError?
- If python is not recognized, try python3 instead. Some macOS systems require python3 even inside a virtual environment.
- Make sure (venv) is still in your prompt. If your terminal session restarted, you need to activate the virtual environment again.
- If the textblob import fails with a missing resource error, re-run python -m textblob.download_corpora.
- help me fix my Python import error
Your Python environment is locked and loaded. Next up, you will build the NLP analysis API that turns raw text into actionable writing metrics.
Build Your NLP Analysis API
Your Python environment is ready with Flask and the NLP libraries installed. Now it's time to build the core of your project: a REST API that accepts any text and returns a detailed analysis report.
Without a working API, neither a web frontend nor a Java client has anything to talk to. This step gets that single endpoint running so you can send text in and get analysis back.
In this step, get ready to:
- Create app.py with a Flask web server and a /analyze POST endpoint.
- Wire up TextBlob and textstat to perform sentiment and readability analysis.
- Test the live endpoint with Postman and see a JSON response.
Create app.py with the Flask app and input handling
Every REST API needs a server to listen for requests and a route to handle them. You will create a single file that sets up Flask and defines a /analyze endpoint.
- Create a new file called app.py in your project folder and add the following code:
from flask import Flask, request, jsonify
from textblob import TextBlob
import textstat
# Create the Flask application
app = Flask(__name__)
# Define the /analyze endpoint that only accepts POST requests
@app.route("/analyze", methods=["POST"])
def analyze():
# Parse the JSON body sent by the client
data = request.get_json()
text = data.get("text", "")
# Return an error if the text is empty or whitespace
if not text.strip():
return jsonify({"error": "No text provided"}), 400
What does this code do?
- The three imports bring in Flask (web server), TextBlob (sentiment), and textstat (readability).
- app = Flask(__name__) creates the application instance that handles incoming HTTP requests.
- @app.route("/analyze", methods=["POST"]) registers the analyze function to respond only when a client sends a POST request to /analyze.
- request.get_json() reads the JSON body from the request so you can extract the text field.
- The if not text.strip() guard rejects empty submissions with a 400 error before any analysis runs.
Add the NLP analysis and return the results
The input handling is done. Now you will use TextBlob for sentiment and textstat for readability, then return everything as JSON.
- In app.py, add the following code directly below the return jsonify({"error": "No text provided"}), 400 line:
# Sentiment analysis using TextBlob
blob = TextBlob(text)
sentiment_polarity = round(blob.sentiment.polarity, 3)
sentiment_subjectivity = round(blob.sentiment.subjectivity, 3)
# Readability metrics using textstat
flesch_score = textstat.flesch_reading_ease(text)
grade_level = textstat.flesch_kincaid_grade(text)
word_count = len(text.split())
difficult_words = textstat.difficult_words(text)
# Return all metrics as a JSON response
return jsonify({
"sentiment_polarity": sentiment_polarity,
"sentiment_subjectivity": sentiment_subjectivity,
"flesch_reading_ease": flesch_score,
"grade_level": grade_level,
"word_count": word_count,
"difficult_words": difficult_words
})
# Start the server when running this file directly
if __name__ == "__main__":
app.run(debug=True)
What does this code do?
- TextBlob(text).sentiment.polarity returns a float from -1 (very negative) to 1 (very positive).
- sentiment.subjectivity returns a float from 0 (objective fact) to 1 (personal opinion).
- textstat.flesch_reading_ease(text) scores how easy the text is to read on a 0-100 scale (higher is easier).
- textstat.flesch_kincaid_grade(text) estimates the US school grade level needed to understand the text.
- The if __name__ == "__main__" block starts the Flask dev server with auto-reload enabled when you run the file with python.
- Save app.py.
✔️ Awesome, I've got everything!
Great. Make sure you have saved app.py before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm your app.py contains these sections in order:
- The three import lines (flask, textblob, textstat).
- app = Flask(__name__) creating the app.
- The @app.route("/analyze", methods=["POST"]) decorator and the analyze() function with input validation, TextBlob analysis, textstat metrics, and the return jsonify(...) at the end.
- The if __name__ == "__main__" block calling app.run(debug=True).
Run the Flask server and test with Postman
Your API code is complete. Time to start the server and prove it works by sending a real request.
- Start the Flask development server by running this command in your terminal:
python app.py
You should see output indicating the server is running on http://127.0.0.1:5000. The terminal will stay occupied while the server is running.
What does debug=True do?
Debug mode automatically restarts the server whenever you save changes to app.py. It also shows detailed error messages in the terminal if something breaks.
Server won't start?
- Make sure your virtual environment is active (you should see its name in parentheses in your terminal prompt).
- Check that you are in the project folder where app.py lives.
- If you see ModuleNotFoundError, the libraries are not installed in your active environment. Run pip install flask textblob textstat again.
Need more help? Why won't my Flask server start?
Now test the endpoint. Leave the server running and switch to Postman.
- In Postman, click the + button to create a new request tab.
- Change the method dropdown from GET to POST.
- Enter this URL in the request bar:
http://127.0.0.1:5000/analyze
- Click the Body tab below the URL bar.
- Select raw, then change the dropdown on the right from Text to JSON.
- Paste this JSON into the body:
{
"text": "I absolutely love learning new programming languages. It makes me feel confident and excited about my career."
}
Before you hit Send, what do you think the sentiment polarity will be for this text? Positive, negative, or neutral?
- Click Send.
You should see a JSON response in the lower panel with all six analysis fields. The sentiment_polarity value is positive (above 0) because the sample text uses words like "love", "confident", and "excited".
What should I see?
Your response will look something like this (exact numbers may vary slightly):
{
"difficult_words": 3,
"flesch_reading_ease": 70.5,
"grade_level": 7.0,
"sentiment_polarity": 0.625,
"sentiment_subjectivity": 0.75,
"word_count": 18
}
The exact values depend on the text you send. What matters is that you see all six fields returned with numeric values.
Getting an error response?
- If you see a connection error, make sure your Flask server is still running in the other terminal.
- If you see 405 Method Not Allowed, double-check that the method is set to POST (not GET).
- If you see {"error": "No text provided"}, confirm you selected raw and JSON in the Body tab and that your JSON has the "text" key.
Still stuck? Help me debug my Postman request to the Flask API.
Your API is live and responding to requests. Next up, you will build a web frontend that gives users a visual interface to interact with this endpoint.
Build the Web Frontend
Your API works in Postman, but a normal user would never open Postman to analyze their writing. They need a web page with a text box and a button.
In this step, you will build an HTML frontend that sends text to your Flask API using JavaScript's fetch() function. You will open the page in your browser and try to use it.
In this step, get ready to:
- Create an HTML page with a textarea, a styled button, and a results section.
- Write JavaScript that POSTs the textarea content to your Flask API.
- Open the page in a browser and submit text to see what happens.
Create the HTML page structure
Your frontend lives in a single index.html file. It contains the page layout, CSS styling, and JavaScript all in one place.
- Create a new file called index.html inside your smart-content-analyzer folder (the same folder that holds app.py).
- Paste the following code into index.html to set up the HTML skeleton and CSS styles:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Content Analyzer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 40px 20px; background: #f5f5f5; color: #333; }
h1 { text-align: center; margin-bottom: 30px; color: #1a1a2e; }
textarea { width: 100%; height: 150px; padding: 15px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px; resize: vertical; font-family: inherit; }
textarea:focus { outline: none; border-color: #4a90d9; }
button { display: block; width: 100%; padding: 15px; margin-top: 15px; background: #4a90d9; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; }
button:hover { background: #357abd; }
#results { margin-top: 30px; display: none; }
.metric { background: white; padding: 20px; border-radius: 8px; margin-bottom: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.metric-label { font-size: 14px; color: #666; margin-bottom: 5px; }
.metric-value { font-size: 24px; font-weight: bold; }
.positive { color: #27ae60; } .negative { color: #e74c3c; } .neutral { color: #f39c12; }
#error-message { color: #e74c3c; margin-top: 15px; display: none; }
</style>
</head>
What does this code do?
This sets up the invisible foundation of your page. The <style> block defines how every element will look: a centered layout, a rounded textarea, a blue button, and card-style result boxes.
The colour classes (.positive, .negative, .neutral) will colour-code sentiment results later.
- Directly below the </head> tag, paste the body section that creates the visible page elements:
<body>
<h1>Smart Content Analyzer</h1>
<textarea id="text-input" placeholder="Paste your text here..."></textarea>
<button id="analyze-btn" onclick="analyzeText()">Analyze Text</button>
<p id="error-message"></p>
<div id="results">
<div class="metric"><div class="metric-label">Sentiment</div><div class="metric-value" id="sentiment-value"></div></div>
<div class="metric"><div class="metric-label">Subjectivity</div><div class="metric-value" id="subjectivity-value"></div></div>
<div class="metric"><div class="metric-label">Reading Ease</div><div class="metric-value" id="reading-ease-value"></div></div>
<div class="metric"><div class="metric-label">Grade Level</div><div class="metric-value" id="grade-level-value"></div></div>
<div class="metric"><div class="metric-label">Word Count</div><div class="metric-value" id="word-count-value"></div></div>
<div class="metric"><div class="metric-label">Difficult Words</div><div class="metric-value" id="difficult-words-value"></div></div>
</div>
<script>
</script>
</body>
</html>
What does this code do?
The <textarea> is where the user pastes their text. The <button> triggers analyzeText() when clicked.
The #results div is hidden by default (display: none). Each .metric card will display one analysis value (sentiment, reading ease, grade level, etc.) once the API responds.
- Save index.html.
- Open index.html in your browser by double-clicking the file in Finder (macOS) or File Explorer (Windows).
You should see a light grey page with a heading, a textarea with placeholder text, and a blue Analyze Text button.
Don't see the styled page?
- Make sure you saved index.html after pasting both code sections.
- Check that the </head> closing tag sits between the two sections you pasted. The first section ends with </head> and the second starts with <body>.
- If you see raw HTML text instead of a styled page, confirm the file extension is .html and not .txt.
Help me debug my index.html page layout
Write the JavaScript fetch function
The button is wired to call analyzeText(), but that function does not exist yet. You need JavaScript that grabs the textarea content and sends it to your Flask API as a POST request.
- In index.html, find the empty <script> tags near the bottom of the file.
- Paste the following JavaScript function between the <script> and </script> tags:
function analyzeText() {
// Get the text the user typed and the display elements
var text = document.getElementById("text-input").value;
var errorMsg = document.getElementById("error-message");
var results = document.getElementById("results");
// Reset the display before each request
errorMsg.style.display = "none";
results.style.display = "none";
// Don't send empty text to the API
if (!text.trim()) { errorMsg.textContent = "Please enter some text."; errorMsg.style.display = "block"; return; }
// Send the text to the Flask API as JSON
fetch("http://127.0.0.1:5000/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: text }) })
.then(function(response) { if (!response.ok) { throw new Error("Server returned " + response.status); } return response.json(); })
.then(function(data) { results.style.display = "block"; })
.catch(function(error) { errorMsg.textContent = "Error: " + error.message; errorMsg.style.display = "block"; });
}
What does this code do?
The function reads the textarea value, then uses fetch() to send a POST request to http://127.0.0.1:5000/analyze with the text as JSON.
If the request succeeds, it shows the results div. If it fails, it displays the error message to the user.
- Save index.html.
✔️ Awesome, I've got everything!
Great. Double check you have saved index.html with all three sections in place: the <head> with styles, the <body> with HTML elements, and the <script> with the analyzeText() function.
ⓧ I'd like to double check the full code
Scroll up and confirm your index.html contains these three sections in order:
- The <head> section with all CSS styles (the first code block you pasted).
- The <body> section with the textarea, button, error paragraph, results div, and empty script tags (the second code block).
- The analyzeText() function inside the <script> tags (the third code block).
Test the frontend and observe the result
Your Flask server should still be running from the previous step. If you stopped it, switch to your terminal and start it again with python app.py.
- Switch to your browser where index.html is open.
- Paste some text into the textarea (any sentence will do).
Before you click the button, what do you think will happen? The JavaScript sends a request to your running Flask server, so you might expect to see analysis results appear.
- Click Analyze Text.
- Open your browser's developer console by pressing F12 (or Cmd+Option+I on macOS) and click the Console tab.
You will see an error message on the page and a red error in the console mentioning CORS policy. The browser blocked your request.
Why did the request fail?
Browsers enforce a security rule called the Same-Origin Policy. Your HTML file is opened from the file system (file://), but it tries to reach http://127.0.0.1:5000. Those are different origins.
The browser sends a preflight request asking the server if cross-origin requests are allowed. Your Flask server does not include the required CORS headers, so the browser refuses to complete the request. This is a real-world problem every web developer encounters on day one.
This error is expected
The CORS error is intentional. Your frontend code is correct. The fix requires a one-line change on the server side, which you will apply in the next step.
Your frontend is built and your JavaScript is correct. Next up, you will fix the CORS error with a single line of code on the server and bring your analysis results to life.
Fix CORS and Complete the Web UI
In the previous step, you saw the browser block your request with a CORS error. That error proves something important: browsers enforce strict security rules about which origins can talk to each other.
In this step, you will fix the CORS error with a single library and then upgrade your JavaScript to display the full analysis results with colour-coded sentiment indicators.
In this step, get ready to:
- Enable CORS in your Flask API so the browser allows frontend requests.
- Update your JavaScript to parse and display each metric with labels and colour indicators.
- Test with different text samples and observe how scores change.
Add Flask-CORS to your API
The browser blocked your request because the HTML file (opened from the filesystem) and the Flask server (running on http://127.0.0.1:5000) are different origins. Flask-CORS adds the Access-Control-Allow-Origin header that tells browsers the request is allowed.
- In app.py, find the imports at the top of the file:
from flask import Flask, request, jsonify
from textblob import TextBlob
import textstat
app = Flask(__name__)
- Add from flask_cors import CORS as the second import line, and add CORS(app) directly below app = Flask(__name__), so the top of your file looks like this:
from flask import Flask, request, jsonify
from flask_cors import CORS
from textblob import TextBlob
import textstat
app = Flask(__name__)
CORS(app)
What does CORS(app) do?
This single line wraps your entire Flask app with CORS headers. Every response your API sends now includes Access-Control-Allow-Origin: *, which tells browsers that any origin is allowed to make requests.
For a local development project this is fine. In production, you would restrict it to specific domains.
- Save app.py.
- Switch back to the terminal where your Flask server is running.
- Stop the server by pressing Ctrl+C.
- Restart the server by running:
python app.py
Before you test, do you think the CORS error will disappear now? What will you see in the results section?
- Refresh index.html in your browser.
- Paste any text and click Analyze Text.
- Open the developer console with F12 and check the Console tab.
The CORS error is gone. The results section appears, but the metric values are empty. That is expected because the current JavaScript does not populate them yet.
Still seeing the CORS error?
- Make sure you saved app.py before restarting the server.
- Confirm you stopped the old server with Ctrl+C and started a fresh one. The old process does not pick up new code.
- Check that CORS(app) appears after app = Flask(__name__), not before it.
Help me debug why my CORS error is still appearing after adding flask-cors.
Update the JavaScript to display results
The CORS barrier is gone, but your JavaScript currently does nothing with the API response. You need to parse the JSON data and populate each metric card with the correct value.
- In index.html, first update the textarea placeholder to give users better guidance. Find this line:
<textarea id="text-input" placeholder="Paste your text here..."></textarea>
- Replace it with:
<textarea id="text-input" placeholder="Paste your text here (email, LinkedIn post, resume bullet, cover letter...)"></textarea>
- Next, update the metric labels to show helpful context. Find these two metric label divs:
<div class="metric-label">Subjectivity</div>
...
<div class="metric-label">Reading Ease</div>
- Replace them with:
<div class="metric-label">Subjectivity (0 = Objective, 1 = Subjective)</div>
...
<div class="metric-label">Reading Ease (0-100, higher = easier)</div>
- Now for the main change. Inside the <script> tag, find the empty text check line:
if (!text.trim()) { errorMsg.textContent = "Please enter some text."; errorMsg.style.display = "block"; return; }
- Replace that line with:
if (!text.trim()) { errorMsg.textContent = "Please enter some text to analyze."; errorMsg.style.display = "block"; return; }
- Finally, find the .then handler that currently just shows the results div:
.then(function(data) { results.style.display = "block"; })
- Replace it with this code that parses each metric and colour-codes the sentiment:
.then(function(data) {
var polarity = data.sentiment_polarity;
var sentimentEl = document.getElementById("sentiment-value");
if (polarity > 0.1) { sentimentEl.textContent = "Positive (" + polarity + ")"; sentimentEl.className = "metric-value positive"; }
else if (polarity < -0.1) { sentimentEl.textContent = "Negative (" + polarity + ")"; sentimentEl.className = "metric-value negative"; }
else { sentimentEl.textContent = "Neutral (" + polarity + ")"; sentimentEl.className = "metric-value neutral"; }
document.getElementById("subjectivity-value").textContent = data.sentiment_subjectivity;
document.getElementById("reading-ease-value").textContent = data.flesch_reading_ease;
document.getElementById("grade-level-value").textContent = "Grade " + data.grade_level;
document.getElementById("word-count-value").textContent = data.word_count;
document.getElementById("difficult-words-value").textContent = data.difficult_words;
results.style.display = "block";
})
What does this code do?
- It reads sentiment_polarity from the API response and assigns a colour class: green for positive (above 0.1), red for negative (below -0.1), or amber for neutral.
- Each remaining metric is placed into its matching DOM element by ID, populating the cards you built in the HTML.
- The results.style.display = "block" line makes the entire results section visible once all values are filled.
- Save index.html.
- Refresh the page in your browser.
- Paste the text "I am very happy with this excellent product" into the textarea and click Analyze Text.
You should see all six metric cards filled in. The sentiment reads "Positive" in green text, with a numerical polarity value next to it.
Metrics not appearing?
- Open the developer console (F12) and look for JavaScript errors. A typo in an element ID like sentiment-value will cause a null reference.
- Make sure you removed the old .then(function(data) { results.style.display = "block"; }) line entirely and replaced it with the new block. Having both causes a syntax error.
- Confirm your Flask server is still running in the terminal. If it crashed, restart it with python app.py.
Help me debug why my metrics are not appearing after updating the JavaScript.
✔️ Awesome, I've got everything!
Great. Double check you have saved both app.py and index.html.
ⓧ I'd like to double check the full code
Confirm your files contain these sections in order:
app.py:
- Imports: flask, flask_cors, textblob, textstat.
- App creation with CORS(app) directly below app = Flask(__name__).
- The /analyze route unchanged from the previous step.
- The if __name__ == "__main__" block at the bottom.
index.html:
- The textarea placeholder now reads "Paste your text here (email, LinkedIn post, resume bullet, cover letter...)".
- The Subjectivity label reads "Subjectivity (0 = Objective, 1 = Subjective)".
- The Reading Ease label reads "Reading Ease (0-100, higher = easier)".
- The .then handler reads data.sentiment_polarity and colour-codes it, then fills in subjectivity, reading ease (plain number), grade level (prefixed with "Grade "), word count, and difficult words.
- The empty-text error message reads "Please enter some text to analyze."
Test with different text samples
The real proof that your analyzer works is seeing the scores shift for different writing styles. Try a formal sample and a casual one side by side.
Before you test, predict: which text do you think will have a higher reading ease score, a formal email or a casual chat message?
- Clear the textarea and paste this formal text:
The quarterly performance metrics indicate a substantial improvement in operational efficiency, demonstrating that our strategic realignment has yielded measurable outcomes across all departments.
- Click Analyze Text and note the reading ease and grade level values.
- Now clear the textarea and paste this casual text:
Hey! Just wanted to say thanks for helping me out yesterday. You really saved the day and I owe you one.
- Click Analyze Text again and compare the results.
The casual message scores significantly higher on reading ease (easier to read) and at a lower grade level. The formal text is harder to read and at a higher grade level. The sentiment also shifts: the casual message reads as positive, while the formal text is more neutral.
Why do the scores differ?
Flesch Reading Ease penalises long words and long sentences. Corporate language tends toward both, pushing the score down and the grade level up.
Sentiment polarity picks up emotionally charged words like "thanks" and "saved" in the casual message. The formal text uses factual language that scores close to zero (neutral).
Your web frontend is now fully functional. It sends text to your Python API and displays colour-coded results. Next up, you will build a Java command-line client that talks to the exact same API, proving that REST services are truly language-agnostic.
Build the Java CLI Client
Your web frontend is working. Any user can paste text, hit Analyze, and see colour-coded results in the browser.
That proves the API works for JavaScript. But what about other languages? The real interview insight is showing that a REST API is language-agnostic. In this step, you will build a Java command-line client that calls the exact same /analyze endpoint using Java's built-in HttpClient with zero external libraries.
In this step, get ready to:
- Create a Java file that reads user input from the terminal.
- Send an HTTP POST request to your Flask API from Java.
- Parse the JSON response and print a formatted results table.
Create the Java file with user input handling
Java's Scanner class reads text from the terminal line by line. You will use it to collect multi-line input until the user presses Enter on an empty line.
- Open a new terminal window or tab. Your Flask server is still running in the original terminal, so you need a separate one for Java.
- Navigate to your project folder (the same folder containing app.py and index.html).
- Create a new file called ContentAnalyzer.java and add the following code:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Scanner;
public class ContentAnalyzer {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Smart Content Analyzer (Java Client) ===");
System.out.println("Enter text to analyze (press Enter twice to submit):");
System.out.println();
StringBuilder textBuilder = new StringBuilder();
String line;
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.isEmpty()) { break; }
textBuilder.append(line).append(" ");
}
String text = textBuilder.toString().trim();
if (text.isEmpty()) { System.out.println("No text provided. Exiting."); scanner.close(); return; }
What does this code do?
The import statements pull in Java's built-in HTTP and input classes. No external libraries needed.
The while (scanner.hasNextLine()) loop reads lines until the user submits an empty line (presses Enter twice). This lets the user paste multi-line text like a full paragraph.
- Save ContentAnalyzer.java.
- Confirm the file exists by running:
ls ContentAnalyzer.java
You should see ContentAnalyzer.java printed in the output.
Don't see the file?
Make sure you saved the file in the same folder as app.py. Run ls without arguments to see all files in the current directory and confirm you are in the right place.
Help me find where my Java file was saved.
Send an HTTP POST request to the Flask API
Java's HttpClient (built into JDK 11+) can send HTTP requests the same way your browser's fetch() does. You will build a POST request with a JSON body targeting your running Flask server.
- In ContentAnalyzer.java, add the following code directly below the last line you wrote (the text.isEmpty() check):
String jsonBody = "{\"text\": \"" + text.replace("\"", "\\\"") + "\"}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://127.0.0.1:5000/analyze"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
What does this code do?
The jsonBody line manually builds a JSON string like {"text": "your input here"}. It escapes any quotes in the user's text so the JSON stays valid.
HttpRequest.newBuilder() constructs the request with the same URL, header, and POST method your JavaScript fetch() used. The client.send() call fires the request and waits for the response.
- Save ContentAnalyzer.java.
Parse the JSON response and print results
The Flask API returns JSON. Rather than adding a library like Gson, you will write a small helper method that extracts values by searching for key names in the JSON string. This keeps the project at zero external dependencies on the Java side.
- In ContentAnalyzer.java, add the response handling code directly below the client.send() line:
if (response.statusCode() == 200) {
String body = response.body();
System.out.println();
System.out.println("=== Analysis Results ===");
System.out.println();
String polarity = extractValue(body, "sentiment_polarity");
String subjectivity = extractValue(body, "sentiment_subjectivity");
String readingEase = extractValue(body, "flesch_reading_ease");
String gradeLevel = extractValue(body, "grade_level");
String wordCount = extractValue(body, "word_count");
String difficultWords = extractValue(body, "difficult_words");
double polarityNum = Double.parseDouble(polarity);
String sentimentLabel;
if (polarityNum > 0.1) { sentimentLabel = "Positive"; }
else if (polarityNum < -0.1) { sentimentLabel = "Negative"; }
else { sentimentLabel = "Neutral"; }
System.out.println("Sentiment: " + sentimentLabel + " (" + polarity + ")");
System.out.println("Subjectivity: " + subjectivity);
System.out.println("Reading Ease: " + readingEase + " / 100");
System.out.println("Grade Level: " + gradeLevel);
System.out.println("Word Count: " + wordCount);
System.out.println("Difficult Words: " + difficultWords);
} else {
System.out.println("Error: Server returned status " + response.statusCode());
System.out.println(response.body());
}
- Now add the extractValue helper method and the closing braces to complete the file. Add this directly below the code you just wrote:
scanner.close();
}
private static String extractValue(String json, String key) {
int keyIndex = json.indexOf("\"" + key + "\"");
if (keyIndex == -1) return "N/A";
int colonIndex = json.indexOf(":", keyIndex);
int endIndex = json.indexOf(",", colonIndex);
if (endIndex == -1) { endIndex = json.indexOf("}", colonIndex); }
return json.substring(colonIndex + 1, endIndex).trim();
}
}
What does this code do?
The if (response.statusCode() == 200) block runs only when the API returns a success. It extracts each metric from the JSON string using extractValue(), then applies the same sentiment label logic (Positive/Negative/Neutral) that your web frontend uses.
The extractValue method is a simple string parser. It finds the key name in the JSON, locates the colon after it, then grabs everything between the colon and the next comma (or closing brace for the last value). This avoids adding any external JSON library.
- Save ContentAnalyzer.java.
- Compile the file by running:
javac ContentAnalyzer.java
If the compilation succeeds, you will see no output at all. That means the code is valid.
Seeing a compilation error?
If you see cannot find symbol, double-check that the extractValue method is placed after the closing brace of main but still inside the ContentAnalyzer class (before the final }).
If you see error: class, interface, or enum expected, you likely have a mismatched brace. Make sure you have exactly two closing braces at the end: one for main and one for the class.
Help me fix my Java compilation error.
Before you run the program, what do you think will happen when you type a sentence and press Enter twice? Will the output match what your web frontend shows for the same text?
- Run the program:
java ContentAnalyzer
- Type a sentence (for example: I love building software that makes a real difference in the world) and press Enter twice to submit.
You should see a formatted table with Sentiment, Subjectivity, Reading Ease, Grade Level, Word Count, and Difficult Words printed in the terminal.
Why do the values match the web frontend?
Both the JavaScript frontend and the Java client send the same JSON body to the same /analyze endpoint. The Flask API processes the text identically regardless of which client sent it. This is the core principle of REST: one API, multiple consumers in any language.
- Try the same sentence in your web frontend (paste it into the textarea and click Analyze Text). Confirm the sentiment and scores match what Java printed.
Getting a connection refused error?
Make sure your Flask server is still running in the other terminal. If it stopped, restart it by running python app.py in the original terminal window.
My Java program can't connect to the Flask API.
✔️ Awesome, I've got everything!
Great. Double check you have saved ContentAnalyzer.java and it compiles cleanly with no errors.
ⓧ I'd like to double check the full code
Your ContentAnalyzer.java has four sections you built in order. Scroll up and confirm each one is present:
- Imports and the main method signature with the Scanner input loop (ending at the text.isEmpty() check).
- The jsonBody string, HttpClient creation, HttpRequest builder, and client.send() call.
- The if (response.statusCode() == 200) block with six extractValue calls, sentiment label logic, and six System.out.println lines.
- The scanner.close() line, the closing brace of main, and the extractValue helper method before the final class brace.
If any section is missing, scroll up to the matching substep and re-add it. Then recompile with javac ContentAnalyzer.java to check for errors.
You now have two independent clients consuming the same API. One runs in the browser, one runs in the terminal with zero external dependencies. Next up, take on the Secret Mission and add a writing suggestions feature.
Secret mission
Add Writing Suggestions
Your analyzer reports scores but doesn't help users improve their writing. Can you add a suggestions endpoint that checks scores against thresholds and returns actionable writing tips? You will need to update all three files: the Python API, the web frontend, and the Java client.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The Flask development server (running in your terminal).
- Your project folder on the Desktop (contains app.py, index.html, ContentAnalyzer.java, and the Python virtual environment).
- TextBlob NLTK corpora (downloaded to ~/nltk_data).
Keep everything running
No action needed. Choose this if you're still actively building or experimenting with different text samples.
- Your project files stay on the Desktop ready to use anytime.
- The Flask server only runs when you start it manually, so it uses no resources when stopped.
- The NLTK corpora in ~/nltk_data take up minimal disk space and are reusable for any future TextBlob or NLTK projects.
Pause - I'll come back to this later
Shut down running processes to free up your terminal. Your files stay on disk for next time.
- Switch to the terminal where Flask is running and press Ctrl+C to stop the server.
- If you activated the virtual environment, type deactivate to exit it.
- Your project folder and all files remain on the Desktop. To resume later, navigate to the folder, activate the virtual environment, and run python app.py again.
Delete - I don't want to use this again
Remove all project resources and start fresh. This cannot be undone.
- Switch to the terminal where Flask is running and press Ctrl+C to stop the server.
- Delete your project folder (which includes the virtual environment and all installed packages) by running:
macOS
cd ~/Desktop
rm -rf smart-content-analyzer
Windows
cd ~\Desktop
rmdir /s /q smart-content-analyzer
- Remove the TextBlob NLTK corpora by running:
macOS
rm -rf ~/nltk_data
Windows
rmdir /s /q %USERPROFILE%\nltk_data
Other NLTK projects
If you have other projects that use TextBlob or NLTK, skip deleting ~/nltk_data. Multiple projects share this data.
Nice Work!
Nice Work!
You just built a full polyglot application from scratch. You have a running NLP-powered REST API with two independent clients consuming it, proving you can design language-agnostic services and integrate multiple programming languages around a single API.
Here's what you accomplished:
- Built a Python Flask REST API that performs NLP text analysis using TextBlob for sentiment and textstat for readability scoring.
- Diagnosed and fixed a real-world CORS error that every web developer encounters when a browser-based frontend talks to a separate API server.
- Proved that REST APIs are language-agnostic by building both an HTML + JavaScript web frontend and a Java command-line client that consume the exact same endpoint.
- Secret Mission: Added a writing suggestions feature with a new /suggestions endpoint consumed by both the web frontend and the Java CLI.
Ready to quiz yourself?