AI-Assisted Development
Development Phase Frontend
Wiring up Our Project
In this lesson, we will connect the React UI to the Flask backend API of our image optimizer application. With both the backend and frontend already developed, it’s now time to integrate them for a seamless user experience.
Activating the Backend
Begin by navigating to your image optimizer application directory and activating your virtual environment. After that, start your Flask application with the following commands:
jeremy@Jeremys-Mac-Studio imageoptimizer.app % ls
__pycache__ requirements.txt venv run.py
(venv) jeremy@Jeremys-Mac-Studio imageoptimizer.app % flask run
* Serving Flask app 'run.py'
* Debug mode: off
WARN:werkzeug:WARNING: This is a development server. Do not use it in a producti
on deployment. Use a production WSGI server instead.
* Running on https://127.0.0.1:5173/ (Press CTRL+C to quit)
Nov/2024 14:19:371 "POST /upload HTTP/1.1" 200 -
Nov/2024 14:19:421 "POST /upload HTTP/1.1" 200 -
Note
For testing purposes, you can use Postman to verify that your API is running correctly.
With the backend active, it’s time to connect it with your React application.
Connecting the React Application
Within your React app, update the API endpoint so that it correctly points to your Flask backend. Below is the core functionality for image uploading through React:
async function handleSubmit(e) {
e.preventDefault();
if (!selectedImage) {
alert('Please select an image first');
return;
}
const formData = new FormData();
formData.append('image', selectedImage);
formData.append('quality', quality);
try {
const response = await fetch('http://127.0.0.1:5000/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
const data = await response.json();
console.log('Upload successful:', data);
} else {
console.error('Upload failed');
}
} catch (error) {
console.error('Error uploading image:', error);
}
}
A complete example of the component could look like this:
import React, { useState, useEffect } from 'react';
function App() {
const [selectedImage, setSelectedImage] = useState(null);
const [quality, setQuality] = useState(80);
const [optimizedImageUrl, setOptimizedImageUrl] = useState(null);
const [selectedImageSize, setSelectedImageSize] = useState(null);
const [optimizedImageSize, setOptimizedImageSize] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
if (!selectedImage) {
alert('Please select an image first');
return;
}
const formData = new FormData();
formData.append('image', selectedImage);
formData.append('quality', quality);
try {
const response = await fetch('http://127.0.0.1:5000/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
// When the API returns binary image data
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
setOptimizedImageUrl(imageUrl);
setOptimizedImageSize(blob.size);
} else {
console.error('Upload failed');
}
} catch (error) {
console.error('Error uploading image:', error);
}
};
const handleImageSelect = (e) => {
const file = e.target.files[0];
if (file) {
setSelectedImage(file);
setSelectedImageSize(file.size);
}
};
// Cleanup object URLs to prevent memory leaks.
useEffect(() => {
return () => {
if (optimizedImageUrl) URL.revokeObjectURL(optimizedImageUrl);
};
}, [optimizedImageUrl]);
return (
<div className="container">
<header className="header">
<h1>Image Optimizer</h1>
</header>
<main className="main-content">
<form onSubmit={handleSubmit} className="upload-form">
<div className="upload-section">
<label htmlFor="image-upload">Upload Image</label>
<input
id="image-upload"
type="file"
accept="image/*"
onChange={handleImageSelect}
/>
</div>
<button type="submit">Optimize Image</button>
</form>
{/* Image Preview Section */}
{selectedImage && (
<div className="preview-section">
<h2>Original Image:</h2>
<p>File size: {selectedImageSize} bytes</p>
<img
src={URL.createObjectURL(selectedImage)}
alt="Original"
style={{ maxWidth: '100%', height: 'auto' }}
/>
</div>
)}
{optimizedImageUrl && (
<div className="preview-section">
<h2>Optimized Image:</h2>
<p>File size: {optimizedImageSize} bytes</p>
<img
src={optimizedImageUrl}
alt="Optimized version"
style={{ maxWidth: '100%', height: 'auto' }}
/>
</div>
)}
</main>
</div>
);
}
export default App;
In this React component, the selected image is uploaded to the Flask backend. Upon a successful API response, the returned binary data is converted into an object URL for display. Both the original and optimized images are shown along with their respective file sizes for easy comparison.
Using Developer Tools
When testing your application, open your browser's developer tools to monitor network activity and validate that the Flask server is accessible at 127.0.0.1:5000
. For instance, after choosing an image like "coolgirl.jpeg" from your computer, you should see a preview similar to the one below:
Handling CORS in Flask
When running the React app and Flask API on separate ports, you might experience CORS errors. To resolve this, install Flask-Cors in your virtual environment:
jeremy@Jeremy’s-Mac-Studio imageoptimizer.app % pip install flask-cors
Next, update your Flask application's initialization file (commonly __init__.py
) to enable CORS:
from flask import Flask
from flask_cors import CORS
def create_app():
app = Flask(__name__)
app.config['DEBUG'] = True
CORS(app, resources={
r"/": {
"origins": ["http://localhost:5173"],
"methods": ["GET", "POST", "OPTIONS"],
"allow_headers": ["Content-Type"]
}
})
from . import routes
app.register_blueprint(routes.bp)
return app
Note
This configuration allows requests solely from the defined origin. Update these settings as needed when deploying to production.
Displaying Optimized Images and File Size Comparison
A major feature of this project is the display of both the original and optimized images, including their file sizes, for side-by-side comparison. Once you click the "Optimize Image" button, the React component processes the binary response from the Flask endpoint and displays the optimized image with its size information. For example:
In some cases, an initial image (e.g., 85.95 kilobytes) might be optimized to 24.94 kilobytes, resulting in a 71% size reduction. Experiment with various quality settings to see how they affect the file size and visual quality.
Final Steps and Deployment Considerations
Before finalizing the frontend integration, consider these additional improvements:
- Store the API endpoint URL as an environment variable to enhance flexibility across different deployment environments.
- Add comprehensive inline documentation in the code.
- Prepare the repository for public release by following best practices on GitHub.
You can find the complete source code for both the Flask backend and the React frontend in the following repository:
Super-Image Optimizer on GitHub
Thank you for following along. With the project now fully integrated, we're ready to explore further enhancements and refinements to our application.
Watch Video
Watch video content