Cloudinary provides SDK's for many programming languages and frameworks. While there is an Upload API endpoint that can be used in both backend and frontend code most developers find the SDK's very helpful.
If you're working with a powerful backend framework like Python Flask, you'll be happy to hear that there is a Python SDK that you can use.
We're going to walk through uploading an image to Cloudinary, but the code we're creating can be easily used to upload any file type including video, and even non-media files.
Very quickly, let's clarify the distinction between Frontend and Backend. Generally, code that runs on the server is Backend and code that runs on the browser is Frontend. However, since code running on the server can render HTML, CSS and JavaScript which all run on the browser, there can be some confusion here. In the context of the Cloudinary SDK's, Backend SDK's can read secret credentials that should not be shared in the Frontend. Backend environment variables need never by exposed in the Frontend. Frontend SDK's can't hide credentials that are meant to be kept secret. Cloudinary provides Unsigned Presets to enable functionality like Upload in browser code without revealing secrets, but if you can write a backend API to perform your upload, you will have a secure upload without revealing your API_SECRET. If you're using Python Flask or Python Django, you can read on to see how to do this using the Python SDK.
The Flask framework makes it easy to define routes and their functionality. We'll create a route named /upload
. This route will accept a POST containing multipart/form-data. We'll package up the image file input into a FormData object in a submit handler, and POST it to our own Flack API. Our API will call Cloudinary Upload API configured with our full set of Cloudinary credentials.
Flask's request
allows us to get data from the client. When submitting files, such as uploading an image file, you can call upon the request option to access the files.
from flask import Flask,render_template, request
if request.method == 'POST':
file_to_upload = request.files['file']
The data retrieved from the request.files['file']
is an instance of werkzeug.FileStorage. The object can be handed off to the Python Upload SDK function. Flask wraps Werkzeug, using it to handle the details of WSGI (Web Server Gateway Interface).
if file_to_upload:
upload_result = cloudinary.uploader.upload(file_to_upload)
app.logger.info(upload_result)
The default upload resource_type
when uploading to Cloudinary is image
. If we want to expand, or create a new, Flask API we can add resource_type: 'video'
or resource_type: 'raw'
if we want to upload video or raw files. Raw refers to non-media file formats including text and JSON.
Finally, the upload_result
is an object that contains the upload response. This response can be returned to the client to complete the actions of our upload
API.
from flask import jsonify
return jsonify(upload_result)
The code for our upload
API is shown below.
@app.route("/upload", methods=['POST'])
def upload_file():
app.logger.info('in upload route')
cloudinary.config(cloud_name = os.getenv('CLOUD_NAME'), api_key=os.getenv('API_KEY'),
api_secret=os.getenv('API_SECRET'))
upload_result = None
if request.method == 'POST':
file_to_upload = request.files['file']
app.logger.info('%s file_to_upload', file_to_upload)
if file_to_upload:
upload_result = cloudinary.uploader.upload(file_to_upload)
app.logger.info(upload_result)
return jsonify(upload_result)
Let's build this app now.
The command below will establish our virtual environment. This is an important step so that we can encapsulate the libraries that we'll be using in this app.
python3 -m venv env
source env/bin/activate
You can also deactivate the environment.
deactivate
source env/bin/activate
python3 -m pip install Flask==1.1.1
The requirements.txt
will keep track of all the versioned libraries needed for the app and is necessary for future deployment.
python3 -m pip freeze > requirements.txt
Your command may vary based on your local python installation. The freeze
will write the library and version to requirements.txt
.
usr/local/opt/[email protected]/bin/python3.9 -m pip install --upgrade pip
python3 -m pip freeze > requirements.txt
Installing Cloudinary will give us access to the Upload API SDK for Python.
python3 -m pip install cloudinary
python3 -m pip freeze > requirements.txt
If we want to access our upload
API from a client that is served from a different host, we'll need to add CORS (Cross Origin Resource Sharing) support.
python3 -m pip install flask-cors
python3 -m pip freeze > requirements.txt
Now, we can add some code to configure the CORS for all APIs and specifically the upload
API. This is redundant but shown to demonstrate both options.
from flask_cors import CORS, cross_origin
app = Flask(__name__)
# somewhere after creating the Flask app you can make all API's allow cross origin access.
CORS(app)
# or a specific API
@app.route("/upload", methods=['POST'])
@cross_origin()
Python makes it easy to load environment variables. The python-dotenv
library is modeled on the Node.js dotenv
package. We'll need 3 environment variable from Cloudinary made available to our code: CLOUD_NAME, API_KEY, and API_SECRET. Don't share API_SECRET. These variables can be exported to the local environment. When we deploy to Heroku, we'll see they have a way to add environment variables so that they are available to the app when running in a Heroku container.
from dotenv import load_dotenv
load_dotenv()
In our upload
API, we use the cloudinary.config()
function to read in the environment variables. The dotenv
library lets us use the os.getenv()
function to access them.
cloudinary.config(cloud_name = os.getenv('CLOUD_NAME'), api_key=os.getenv('API_KEY'),
api_secret=os.getenv('API_SECRET'))
When working locally you can create a gitignore'd .env
file that contains the Cloudinary credentials for local testing. You can find the values for these credentials in your Cloudinary console.
CLOUD_NAME=CLOUD_NAME
API_KEY=API_KEY
API_SECRET=API_SECRET
Install the python-dotenv
library.
python3 -m pip install python-dotenv
python3 -m pip freeze > requirements.txt
We're now ready to test the app locally. We can do some end to end testing using Postman and a local form pointing at a server running on localhost.
You can create your app with an upload function by downloading, cloning or copying from this. Add you credentials to a .env
file. Start your server on localhost.
python3 app.py
This will open a server at http://127.0.0.1:5000/
. Note the default port is 5000.
In Postman, you want to
- Set the method to POST
- Set the body type to
form-data
- Establish Key/Value pairs for input. The Key should be
file
. You'll find that as you hover over the Key field, there is a drop down and you can select Text or File. Select File and then you'll see a Select Files button and you can select a file from your local drive - Now press Send to process the request
- You'll see the Cloudinary Upload API response in the in the Body result at the bottom of the page
Open the index.html
using a local HTTP server. Choose a local file and press upload.
The fetch
command will call the server running on localhost. If it's successful, you'll see the Cloudinary Upload API response in the console.
fetch("http://127.0.0.1:5000/upload", options)
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
Don't forget to event.preventDefault();
when using a submit handler or you'll get a page refresh, and won't see your log.
To post a file in a submit handler, you'll need to create a FormData object and append the file as shown in the code below.
const formData = new FormData();
formData.append("file", fileInput.files[0]);
const options = {
method: "POST",
body: formData,
};
This will package up the file you select in your form input as if you had submitted a multi/part-form. When submitting files, you want to use multi/part a form because each input is sent as a block of data separated by boundaries.
You should observe a result like this in your console.
Heroku is a PAAS (Platform as a Service) that can be used for serving prototypes or production ready applications. We'll use it to host the API we've developed here.
Start by creating a free account on Heroku.
To deploy to Heroku, you need to create a file named Prodfile
. This file gives Heroku instructions on how to start your server. Gunicorn, short for Green Unicorn, is a Python WSGI HTTP Server for Python that we can use to host the app. Install Gunicorn.
python3 -m pip install gunicorn==20.0.4
python3 -m pip freeze > requirements.txt
Then add instructions to start the app using Gunicorn in the Procfile.
web: gunicorn app:app
Create a file named runtime.txt
and add the version of Python you want to use. Use the ver
python-3.9.1
We deploy with Git. Here's a set of commands to do that. You can create the new heroku app using the CLI. If you have created it using the heroku.com dashboard, you can just add the link that heroku created for you in a git:remote
command.
git init
git add .
git commit -m"my first commit"
# create a new heroku app
heroku create
# you should get back a URL where the app will be hosted
git remote -v
# will confirm that you have set up a remote to push to heroku
# if you have an existing heroku app that you created in the Heroku GUI
# you can add it with this commmand instead of creating a new one
heroku git:remote -a thawing-inlet-61413
# to deploy you can now just push to heroku
git push heroku master
# open your server (it's nice to have a GET method so you can verify like this)
heroku open
# if you need to login to heroku from the command line
heroku login
# this will prompt you to open the web page to login
Open the dashboard and navigate to your new server instance. Click on the Settings tab and scroll down to the Config Vars section. If you click on Reveal Config Vars, you'll find you can enter Key/Value pairs here. This is where you should load your Cloudinary environment variables: CLOUD_NAME, API_KEY, API_SECRET.
See the docs for more options on setting up prod and dev instances.
If you have a GET method API like the "hello world" in the app.py example, you can open the heroku app in the browser.
For end to end testing, you can POST to the heroku server link from Postman and you should see similar results as to our local testing.
You can also modify the index.html
file to post to your heroku server. Just change the fetch
command to use your heroku server link.
fetch("https://xxxx.herokuapp.com", options)
You'll notice that I have erased the name of my heroku deployment link in all the images. Deploying this app to heroku enables you to upload data to Cloudinary. If the link is not secured, for example, by using it only on an authenticated web page, anyone can upload to your Cloudinary cloud. Even though you are hiding your environment variables, you have created an public method to change data on your cloud.
This document doesn't cover adding authentication. There are many options available.
Once you get this API running, you can build other Media API's using the Cloudinary Python SDK by following the same pattern.