Discover how to seamlessly send uploaded image data from JavaScript to Flask and receive a temporary file path for further handling. This article guides you step-by-step through establishing a functional round-trip between client-side JavaScript and a Python Flask server.
Key Insights
- Implement a round-trip workflow where JavaScript fetch sends uploaded image data to a Flask server route named "upload," which returns a temporary image URL.
- Learn to instantiate and append uploaded images as binary large objects (blobs) to form data objects in JavaScript for efficient transmission to the server.
- Utilize Flask's request and JSONify methods along with Python's tempfile module to temporarily handle uploaded image files and generate paths for server-side processing.
Note: These materials offer prospective students a preview of how our classes are structured. Students enrolled in this course will receive access to the full set of materials, including video lectures, project-based assignments, and instructor feedback.
This is a lesson preview only. For the full lesson, purchase the course here.
Hello, welcome back to AI apps with Python using Flask, JavaScript, and of course the OpenAI API. My name is Brian McLean. It's nice to have you back.
This is Lesson 13. Now we're on to the second part of our meal analyzer. In the first part, we got the image to upload—right, the meal image that you browse to appearing on the page.
And in this lesson, we're going to send the meal data to the server and get back what's called a temporary path, a temporary image path, which is going to be used to handle the image on the server temporarily. And from that, it will specially encode the data for sending to the OpenAI API. Kind of complicated.
In this lesson, we're just doing the step where we're trying to get this response that you see right here, this jibberishy path back from Flask. We are not interacting with the OpenAI API in this lesson—just trying to do the round trip between fetch on the JS side and Flask on the Python side. And that's a big step forward anyway, because in the last lesson, even though it was really cool what we did in getting the picture to show up, it was all client-side—all JS, browsing to the system with JS handling, displaying the image on the page.
So there was no server, as cool as it is—and wasn't. Now we're going to build on that. We're going to take that image data that we browse to, ship it over to Flask with fetch, hitting a route called upload and getting back this URL just to make sure it's working at that point.
It'll make a lot of sense once we get rolling. We're going to open up. We need 01 HTML as our starter.
This is where we left off—and 01 JS. So let's begin with the HTML. And here we are in Lesson 13 in your book: Sending Image Data to Flask and Returning a Temp URL.
That's the thing you just saw—that long jibberishy string that you were just looking at. In this lesson, we will send our uploaded image data to Flask, which will make a temp file URL and return that to JS. And this way we'll establish the JS to Python back to JS round trip.
The OpenAI API is not yet involved in this lesson as we continue to build step-by-step. Step one: Preparing uploaded image data for transmission to Flask. We're going to take our meal analyzer 01 file, do a Save As, and call the new file 02.
Let's take care of that move. Meal analyzer 01 becomes 02. Now we're going to assign ID tags for displaying the meal info.
We've got four tags already, but they don't have any IDs. We need to be able to target them. We'll give them IDs right now.
And we don't need all the IDs for this lesson, but we want the meal name to just use it to display that jibberishy string rather than make its own little box to output to. So this "Hello from Flask" temp image path that you see here is showing up in where the meal name needs to go ultimately. So let's go to those four tags and assign them their IDs.
So the H3 ID will be meal name description. The P tag will be description. Calories ID will be calorie count.
And the macronutrients, which is your protein, fat, and carbs, will just be called grams. And we do need to update the file path of the JavaScript being imported into the HTML. So that will be meal analyzer 02 JS.
Speaking of which, from meal analyzer 01 JS, Save As and call the new file 02. We're done with the changes to the HTML. So let's move that over here.
Meal analyzer 01, the final file from the last Lesson 12. We're going to do a Save As of that and call it meal analyzer 02 JS. Get the meal name H3.
That's where we're displaying that jibberishy URL that we're getting back—that temp URL that we'll be getting back from Flask. Get the meal name H3. It is for displaying the name of the meal, of course.
But for this lesson, we will use it to display just the temp image URL that we get back from Flask. We'll say get DOM elements for displaying meal info. But we just want to start with the meal name.
We don't need to get all four of them right now. We can wait on that. That will be document query selector hashtag meal dash name.
Have the Analyze Send button call a function when it's clicked. Okay. We already have a listener for the file input.
Let's just copy-paste that. Call a function which runs when user clicks Analyze button, which we're calling the Send button, which is fine. On click, Send button on click, not on change.
We'll be calling send image for analysis. That will be the name of the function. And we need a new variable in the global scope.
Form data is what we'll call it. And this will be for saving a form—for instantiating a FormData object, which will be used to attach the image data, kind of bundle that up and ship it off to the server as form data. We're going to say let form data.
And inside the display upload image function, the existing function from the last lesson, we're going to instantiate a new FormData object using the new keyword, which JavaScript Developers will recognize. It's the new keyword you use to instantiate new images, new audio, and new date, other objects. It's going to return an object, which we will save to that new form data variable.
This is, again, for bundling the data to be sent to the server. Instantiate a FormData object. Form data equals new FormData.
And now we're going to store the uploaded file—image file—blob, as it's literally called, in the FormData object and give it the name of image. And the image is going to be the name of a key, and the value will be the blob. It's literally called a blob.
When you upload some big piece of data—not a little piece of text, but, say, an image like we did or a video—it’s this massive thing, right? Multiple megabytes potentially, as opposed to a much smaller text file. And it's known as a blob—a Binary Large Object. And anyway, that blob is living in this uploaded file variable, and that is what is being appended to the FormData object as the value of a key called image.
Append the uploaded image blob to the FormData object. FormData.append("image", uploaded file). Uploaded image is the value of a key called image, right? So it's a key-value pair we're doing right here.
And we're using append, which is a very Pythonic name, right? That's the method that you add items to a list. In JavaScript, you say push to add items to what's called an array. Step two: We need the function that sends the FormData to the server and handles the response.
We're going to define the function which runs when the Analyze button is clicked. We'll come on down and declare or define that new function. Function send image for analysis.
Define function which runs when user clicks Analyze. Analyze. Analyze button.
It's actually the Send button. If the user clicks Analyze without having chosen an image, though—kind of like when we remember we were in that AI chat assistant—what do you do if the user clicks send without having typed a message? You want to return out, you know, just end the function. Don't do anything.
Don't make a chat bubble. So in this case, we want to tell the user, "Hey, you got to upload an image to analyze." We're going to output that message to the meal name, so it's nice and big right there under the image, and then return to stop the function.
We're using if (!uploadedFile), the NOT operator, the exclamation point in JS. So in JS, the exclamation point is known as a NOT operator, and it is used here as!variableName to check for nonexistence of the variable. If nonexistence is true, that would mean if the uploaded file does not exist, we're going to output a message like, "Hey, you got to upload a file, " and then return.
If user clicked Analyze without uploading image, warn and exit. If (!uploadedImage), we're going to use this nice big H3—let's say textContent—to post our warning. "Upload a meal image to analyze." Forget the please.
Just do it. Okay. And then return.
Exit the function. So no more code runs, right? We don't want to be doing any more of the code in the function if we don't have an image. After the if block, we're going to do a fetch request.
If you make it that far past the if block, the if didn't run, and what do we want to do then? We're going to do a fetch request with "upload" as the URL, and "upload" will be the name of the route that we have to make over in Flask. Do a fetch. It's going to be a fetch then, actually.
We can do the triple chain thing. We'll say fetch('/upload'), again, the name of the route that we're going to make. And for its second argument, we do need another argument here.
Pass in an object with two properties. We've seen this—passing in the method in curly braces. We're also going to pass in a body—the value of the latter being the form data.
So let's say method: 'POST'. We need squiggles here. Method.
We could do this in one line. It's pretty short. Method: 'POST', body: formData.
That should—yep, that should be right. We're going to then chain the first.then onto the end of fetch. The first.then calls.json() on the response object, converting it to a JavaScript object.
We've been calling this input response JSON. It's actually a response object. It's got a little bit more than JSON.
It's got a response code, some other data—but really the JSON is the meat of it, the only thing we care about, really. And that's pretty standard, right? We've used this first.then before with no changes to what we're going to do right now. It's the response coming in that we are parsing to get the object out.
Parse response object. We're going to then chain on the second.then, which is when we handle the object—specifically, we want to update the webpage with the data coming back from the parsed object, you know, the data in the parsed object. That will be the message and image path properties, which we're going to have Flask return.
We haven't written the server yet, but when we do, the return statement of our route function is going to send JSON back with these two properties. Now that we've parsed the JSON, they're available as the msg and imagePath properties of the response object, and we're going to output those to the meal name. We'll say then the response object—we're going to send meat.
What do we want to do with the response object? We want to set the innerHTML of the meal name to be this concatenated string: responseObject.message. The message is going to say, "Hey, this is hello from Flask"—literally. Object of image. Call it image.
What do you want to call it exactly? imagePath, right? It's a full URL. You could call it image. You could call it URL as well.
All right, there we go. And then we'll do the error handling at the end to complete the chain. We'll say that.catch is going to have an error as the input of its callback, and we're going to return a log of just error.
Actually, just error—just the error. Yeah, that's the name of the variable. Okay.
Just log the error. Step three: Now we're going to make the Flask server for receiving the form data from fetch. We're going to make a new file and name it server meal analyzer 02.
Kind of a mouthful, but I want to start with the word server. Keep that going. And we're going to do this one from scratch.
So it would be good practice for us. File → New → Just new file. Server meal analyzer 02.
And we're going to import the required dependencies. We need Flask, render_template to output the index page, request to handle the incoming data from fetch, and jsonify to handle returning JSON. So request is for the incoming JSON, and jsonify is for the outgoing JSON as we complete our round trip from JS fetch to Python Flask back to JS fetch.
We're familiar. We've used all of these methods before, but here they are summarized. So from flask import capital Flask, render_template method, request, and jsonify.
And, of course, we need to instantiate the Flask app, so we'll do that right away. App = Flask(__name__). Define the home route for rendering the HTML page in the browser.
When you hit the home route, it'll run the index file, which will render meal analyzer 02 HTML. app.route('/') → call it index. We're going to return render_template('meal_analyzer_02.html').
Then we're going to define the upload route next. We need another route. So this is the route that just loads the home page.
Now we need the route that handles the fetch request. This is a route requested by JS fetch. We're going to specify methods=['POST'] since the incoming form data will arrive by POST.
That's standard. So I'm going to say @app.route('/upload', methods=['POST']) → def upload().
Methods because you can pass in more than one method. Define the route function. We're going to save the image property.
In the route function, we're going to start by saving the image property from the incoming request. We're going to parse the image property from the incoming request file. This is "image" as named in the JS when we appended the uploaded file to form data.
If you recall, when we did this move, we're declaring a key called "image" and setting its value to be this uploaded file blob. And that image property is what we're unpacking in Flask right here with request.files. A little different from request.body, right? Because request.body is just sending a JSON string.
Here it's files—different, right? This blob file. Image = request.files['image']
Instantiate a file to store the uploaded image on the server. That is next right underneath. So there's this temp file.
Oh wait, do I need to import tempfile? Nope. Oh yeah, you do.
We need to import tempfile. Yep, let's go back.
Let's do this in one move. Import tempfile. We have to make that as its own line because that's not a Flask sub-library like these other ones.
Okay. Import tempfile. We do need that.
Temp_file = tempfile. NamedTemporaryFile(delete=False) → meaning it won't auto-delete with another request coming in, I believe. I haven’t Googled that particular property.
Temp_file = tempfile. NamedTemporaryFile(delete=False). And this is Python—we don’t do camelCase.
NamedTemporaryFile with delete=False. We've declared a temp file.
And we're going to use the temp file to store the image. The image exists, but it's not attached to this temp file yet. We're going to do a parse incoming image blob.
Make temp file for storing the image temporarily. Instantiate a file. Okay, we did that.
Try: now we're going to save the uploaded image to the temporary file. We're going to try—try to save.
We have a try-except in case something goes wrong—we don’t want an error. That’s the point of try. If you just do it and it doesn’t work, you get an error.
If you do a try-except, it gracefully reports an error without throwing an error—like without your code breaking. We’re going to say image.save(). Now, this image that we requested—this blob thing we unpacked—has a.save() method, which is going to take the temporary file name property as its argument. Image.save(temp_file.name). Let’s put the except part so it doesn’t have the squiggle.
That’ll be for errors. Image.save(temp_file.name). Then we’re going to store the temporary file’s path as a string in the image_path variable. This is a string.
Image_path = temp_file.name. We’re going to take the image and save it to the temp file as its name. Then we’re going to take that name and simplify it as a variable.
This is a string—just straight-up assignment. Image_path = temp_file.name → save temp path to var. We’re going to attempt to return all that.
We’re going to return JSON to fetch with one of the properties being that image path. We’re going to try return jsonify and you pass a dictionary that it’ll turn into JSON. We’re going to say message: "Hello from Flask".
We’ll say image: temp image URL, temp image path. We’re going to display that message, and then right after that we’re going to display the actual image path. That’s why we need another property.
I guess we could just CONCATENATE this into one message, but we’ll send two properties. That’s how we’ve got the second.then in the JavaScript all set up. If it doesn’t work, what do we want to do? We’re going to run an exception.
We’re going to jsonify the error. Regardless, we have to send back something. The error will be the stringification of the error—except Exception as e—and the server file as ever outside of the function.
If __name__ == "__main__": app.run(debug=True). Run the server, upload a meal image and click Analyze. We’re going to run the Server 02.
If it works, we’re going to take that URL, go in the browser where the webpage should load. We’ll click Choose File, browse to some meal image. When the image appears, we will click Analyze, and then the message from Flask that we just made should appear, followed by that temp image path, which will be some gibberish.
Let’s see if we got that cookin’ here. Running server, refreshing, Analyze. Oops, error.
What’s it not liking? 36, blob. That’s actually okay. Meal analyzer, blob.
It’s actually called a blob. It worked. That’s just a favicon—that doesn’t matter.
That "failed to load favicon" does not matter. Ignore. We’re not getting an error, but are we getting an error in the console? Let’s see here.
Oh, I don’t know. Let’s see. Nope.
Nope, nope, nope, nope, nope, nope, nope. Okay, what’s going on? It’s almost like the function’s not even running. All right.
This is like literally nothing going on. All right, let’s just log the name of the function or something. Just—does the function even—is the function even running? Yes.
Now what? Upload, methods post, fetch upload. Why does it not—oh gosh, I haven’t closed my if statement. Okay, no wonder.
And everything was all gray. All right. Gosh, all right, that’s why.
I had the fetch inside the if statement, so of course it couldn’t work. Okay, here we go one more time. Boom, there you go.
There is our data coming back from Flask—not just any old data. That gibberish is the temp file that we just sent over. So it is working.
And that concludes this lesson. Good job, everybody. We will see you for the next one whenever you’re ready.