How to Create a Pinterest Clone Part I: Upload Photos

A photo hosting service with image classification and searching powered by TigerGraph

Jim Chen
Geek Culture

--

The preview of the final product

Overview and Objective

Pinterest is a billion-dollar company with growing number of users. One of the technologies that sets Pinterest apart was introduced by Jure Leskovec, the chief scientist at Pinterest and professor at Stanford University, in this video, where he talks about how Pinterest uses graph information of billions of pins and boards to empower AI and provide more relevant recommendations and searching results. Pins alone can generate a graph, where photos are connected to common metadata. This graph information can also be used to obtain better searching results.

Example of the metadata in a picture (Image from Pixabay)

This blog series will show you step-by-step how to build a Pinterest clone that identifies the metadata of user uploaded photos with basic image classification algorithm and uses the metadata with TigerGraph to empower searching. We will use Quasar for the frontend, FastAPI for the backend, MongoDB to store the photo files, and TigerGraph to store the photo metadata and empower searching. By the end of this blog (Part I), you will have built a web application that supports photo uploading with a preview. Feel free to refer to the code posted for both frontend and backend after each step!

Prerequisites:

Basic familiarity with Typescript, Python, and MongoDB are recommended but not necessary! Feel free to explore only parts that you are interested in and copy the code for other parts!

Section I: Create the frontend, backend, and database

Let’s start by creating a directory called photo_library that will contain everything for this project. We will first create our frontend directory with Quasar-CLI. Open a terminal at photo_library and enter:

$ yarn global add @quasar/cli 
$ yarn create quasar
# or: $ npm i -g @quasar/cli
$ npm init quasar

If you haven’t installed yarn and npm, it is recommended to install npm. Please refer to the docs here!

It will install Quasar-CLI with yarn or npm and start creating a quasar app. When prompted to enter the configuration, please refer to:

Cool! It will create the directoryphoto_library/frontend. Now let’s open it in VSCode (or your favorite code editor). If we run $ quasar dev, we will see the default Quasar page shown on http://localhost:8080/#/. Let’s change it into a template page for this project. We can put the following code in src/pages/IndexPage.vue and src/layouts/MainLayout.vue respectively.

If we run $ quasar dev now, we will see a page as follows:

Good job! It already looks (kind of) close to our final product.

We will send API requests to the backend using the ‘axios’ package. Let’s navigate to photo_library/frontend and add "axios": "0.27.2" in "dependencies" of package.json.

Then, we can run

$ yarn
# or:
$ npm install

to install the ‘axios’ package.

All the subsequent code changes will be in src/pages/IndexPage.vue and we will approach our final product iteratively.

Let’s move on to our backend. If you haven’t installed python3, please refer to this if you are on Mac, or this if you are on Windows.

We will start by creating the directory photo_library/backend and two files, main.py and requirements.txt, in it. We then put some initial code in them:

# main.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == '__main__':
uvicorn.run('main:app', reload=True)

and

# requirements.txt
fastapi==0.75.2
uvicorn==0.15.0
pyTigerGraph==0.0.9.9.2
motor==2.5.1
python-multipart==0.0.5
matplotlib==3.5.2

For sake of simplicity, the requirement file has all the modules that will be installed by pip3 in this project (the only one left is tensorflow, we will install it with pip later). So that we don’t mess up our local environment, we will use a virtual environment to install the modules. In a terminal at photo_library/backend, let’s run:

$ python3 -m venv venv
$ source venv/bin/activate

We will see (venv) showing up at the beginning of the terminal prompt line. It means that our terminal is operating in the virtual environment. Please remember to run $ source venv/bin/activate every time you close and reopen the terminal.

Then, we can run

$ pip3 install -r requirements.txt

to install the modules, which will take less than a minute. After that, we run

$ python3 main.py

It will start our backend with the first API endpoint listening on http://localhost:8000/:

Great! We have a working FastAPI backend server now.

We will use MongoDB and TigerGraph to store data.

For MongoDB, we can refer to this guide for the installation and setup of MongoDB Community Edition, and also install Compass, which is the Graphic User Interface (GUI) of MongoDB. After that, open up Compass and click on the button Connect which creates a connection to MongoDB. On the resulting page, click on the Databases tab and click on Create database, then enter photo_library for database name and photos for collection name. Then, we are good with MongoDB!

For TigerGraph, we can refer to this documentation to create our TigerGraph cloud solution. It is a cloud graph database with user-friendly GUI, and it can be used for free! During setup, remember to keep note of your initial password and subdomain! We will usephoto-library.i.tgcloud.io as our subdomain and tigergraph as our password. The solution used for this blog will have already been terminated by the time of publication. Please create a solution with a more secure password.

Once the solution is created, we will click on the start button:

Please notice that the solution will automatically stop after 1 hour of inactivity, but you can restart it anytime

Once started, we can open GraphStudio to create the graph and write queries!

Enter the username (default: tigergraph) and password (the initial password you just set for your solution) to log in to GraphStudio. Then click on Global View in the upper left corner and create a graph called ‘photos’.

Click on ‘Create a Graph’ (The graph ‘photos’ is already created in this example solution)
Enter the name of the graph

Then switch to the graph ‘photos’ by clicking on ‘Global View’ and then ‘photos’ in the drop-down list. Click on the tab ‘Design Schema’ to create a vertex for our graph.

Click on the button marked red in the upper left corner to add a local vertex type, then edit the vertex type as shown in the image. (You will not see the warning. It is a spoiler for a later part of this blog.)

We will create a vertex type ‘Photo’ with an attribute ‘id’ that stores the object ID of a photo stored in MongoDB. Although storing the object ID alone in TigerGraph isn’t useful for now, it will empower searching when combined with tags that we will generate with the image classification algorithm in a later part.

After the changes, click on the ‘Publish schema’ button to the left of the ‘Add local vertex type’ button, wait for the changes to be committed, and our TigerGraph solution is good to go!

Congratulations on setting up the development environment! Let’s have a short break and then move on to implement the photo hosting service!

Section II: Implement the photo hosting service

Let’s start with the backend. We need an API endpoint that takes a photo in the form data as input, stores the photo bytes in a document of MongoDB, and stores the document’s object ID in TigerGraph.

The API endpoint would look like this:

# from fastapi import UploadFile
@app.post("/uploadPhoto/")
async def upload_photo(file: UploadFile):
contents = await file.read()
# add photo to MongoDB
data = { "photo": contents }
photoID = await add_photo_to_MongoDB(data)
# add photoID TigerGraph
success = await add_photo_id_to_TigerGraph(photoID)
if success:
return { "code": 200, "message": "Photo uploaded"}
else:
return { "code": 401, "message": "Failed to add photo"}

We then implement the bold functions that interact with databases. We use Motor to interact with MongoDB:

# import motor.motor_asyncio
####################
# Connection to MongoDB
client = motor.motor_asyncio.AsyncIOMotorClient("mongodb://localhost:27017")
database = client.photo_library
photo_collection = database.get_collection("photos")
# Add a new photo to MongoDB
async def add_photo_to_MongoDB(photo_data:dict):
try:
entry = await photo_collection.insert_one(photo_data)
return str(entry.inserted_id)
except:
return False

Notice that the bold variables refer to the database name and collection name we used earlier. You can change them if you used different names.

We will use pyTigerGraph to interact with TigerGraph. Please change the host and password to your own.

# import pyTigerGraph as tg
####################
# Connection to TigerGraph
conn = tg.TigerGraphConnection(
host="https://photo-library.i.tgcloud.io",
graphname="photos",
username="tigergraph",
password="tigergraph", )
conn.apiToken = conn.getToken(conn.createSecret())
#conn.apiToken = ('...', ..., '...')
async def add_photo_id_to_TigerGraph(id: str):
try:
conn.upsertVertex("Photo", id, {})
return True
except:
return False

There is a little trick to develop with PyTigerGraph: apiToken generation can take several seconds, which can make FastAPI reloading less responsive. If we are constantly changing code, it will be helpful to print out the apiToken once and just set it to the value.

Put them together, main.py now looks like this:

We can start it with python3 main.py (remember to activate the virtual environment with source venv/bin/activate) and visit the documentation page on http://localhost:8000/docs

Expand the tab for the endpoint we just built and click on ‘Try it out’.

Then, we can choose a file (less than 16MB) and click on ‘Execute’. If the MongoDB Community Edition and TigerGraph solution are running properly, we can see a new document added in MongoDB and a vertex with the same object ID added in TigerGraph.

In GraphStudio, you can switch to the graph ‘photos’, click on ‘Explore Graph’, and search for the vertex

We have built a working API endpoint to upload and store photos! Let’s finish this part by building up a simple frontend with some styles. We can put the following code in photo_library/frontend/src/pages/IndexPage.vue.

The resulting page will look like this:

You can click ‘Pick Photo’ to select a photo and then click ‘Upload’ to send the selected photo to the backend. Feel free to play around with it and check if the changes are reflected in the databases.

Congratulations on finishing Part 1! All the code is uploaded here with instructions to run it.

We will incorporate image classification and searching in later parts. Meanwhile, definitely join the TigerGraph Discord and the Developer Forum if you have any questions!

--

--