Building a curated "API" for wallpapers using FastAPI

I really like collecting interesting and aesthetic looking wallpapers for my machines, its sort of become a hobby and over the years I've collected a decent amount of wallpaper-worthy images. Now, I wanted a way to conveniently share these images with friends in a way such that they can be easily searched by categories. For example, if someone's looking for images of cities for their wallpaper then they should be able to query for a city image from the collection. To achieve this, I decided on building a kind of a "Wallpaper API" which allows you to search for images based on various categories and tags in an intuitive way. For example, you should be able to get a city image from the collection by hitting the /city endpoint on the API, or a nature image by hitting the /nature endpoint. The idea felt reasonable enough so I started thinking of the tools that could help me implement this.

The tools for the job

Deciding on the right tools for this task was not as trivial as I had thought. I knew that I'd be using Python for writing the API. Out of Flask and FastAPI, I decided to try out FastAPI because I had never used it before and also due to its performance advantages over Flask. The hard part was figuring out where I could host my images and deploy the API. As a student, I was relying on the free tiers of services but they all have really tight usage limits. I went with Google Cloud Storage for storing my images and it only allows 1GB worth of downloads per day on their free spark plan. For hosting the API online, I went with Render.

So my final setup looked like this:

  • FastAPI for the API
  • SQLite for the database
  • Google Cloud Storage for hosting the images
  • Render for deploying the API

Database design and tagging the images

Now that the tools were decided upon, I wanted to start by creating a database of the images I had. I'd decided to store only the URLs of the images in the DB. My main requirement here was to allow each image to be able to have multiple tags. For example, an image could be tagged both "nature" and "painting". So I wanted a "many-to-many" relationship between my images and tags. An image should be able to have multiple tags and a tag could have multiple images.

I was initially thinking to keep everything in just one table because there are just a few things to store! I'm too lazy. But that would have caused too much duplication if an image was assigned, say 5 or 6 tags. The same image URL would be repeated over and over in the table. So, I finally decided to stay true to the spirit of normalization and split my table into three. A table for image URLs, a table for tag names and another one to act as a link between the two. The link table would keep track of the different tags assigned to each image.

With the database schema now ready, I needed to populate the database with the images. My wallpaper folder has about 200 images. How to assign them tags? Since I wanted each image to be able to have multiple tags, I couldn't figure out any other way apart from manually tagging each image. To make this process slightly less onerous, I wrote a quick and dirty script that prompted me to enter the tags for each of the images and committed them to the database. It still took me roughly an hour to tag all the images. That one hour is now forever lost to the sands of time (well, its not as if I've never squandered time before!).

The API

The task of writing the API itself was simple enough. I wanted to keep it quick, simple and as easy to use as possible so some of the design choices I've made here may feel a little unconventional. Currently, the API has the following endpoints each of which correspond to a tag in the database. There's also a brief description of the kinds of images you can expect at that specific endpoint:

Endpoint
Description
/natureImages featuring natural elements like forests, mountains, lakes, flowers etc. Present in a variety of art-styles.
/ghibliStills captured directly from Studio Ghibli's beautiful movies.
/cityImages including bustling cities, quaint towns, buildings, bridges and other architecture
/abstractPatterns, shapes & colors; experimental and abstract art.
/paintingBeautiful paintings done in a variety of art forms.
/digital_artBreathtaking digital artwork, created using tools like Krita, Blender, Photoshop, etc
/comfyImages that evoke a cozy, 'comfy' feeling. Very subjective.
/bestMy personal favorite images out of the entire collection.
/randomReturns a random image from the collection, could belong to any category.

For example, visiting the /nature endpoint returns a random image tagged "nature" from the collection.

Apart from this, there's one special endpoint : /intersection

It allows you to do an intersection of tags using query parameters and returns a random image from the images that match all of your specified tags.

For example, you can visit the endpoint : /intersection/?tags=city&tags=digital_art to get a random image that has both the tags "city" and "digital_art". This endpoint supports a variable number of tags so you can supply more than two. The API will let you know when an intersection was empty.

For each of the endpoints, the API directly serves you a random image corresponding to the specific tag. I used FastAPI's RedirectResponse class to directly take the users to the URL of an image that matches their tag. This is different from traditional web APIs which serve JSON content but I decided to go with this choice mainly because of its ease of use by the end users.

A caveat with the deployment

The API is currently deployed on Render using its free tier. You can access the deployment at:
https://wallzy.onrender.com

There's just one caveat with Render's free tier: it spins down the server after 15 minutes of inactivity and starts it back up when a new request comes in, which usually takes a minute or so. While I try to keep it active by running a script that pings the API every so often, you may find that it is taking a while to respond. Don't worry, it will come online soon(about a minute) after you've made a request.

You can also choose to run the API server locally which should always work perfectly. Its as simple as pip installing a few requirements and opening http://localhost:8000 on your browser. The instructions can be found on the GitHub page.

Usage

You can start using the API with your browser via the render deployment. Just hit the endpoint for the tag you'd like to get the images for. For example, visit https://wallzy.onrender.com/digital_art to get a random image tagged digital_art from the collection. You just need to suffix the tag name at the end of the base URL.

If using it in the browser feels cumbersome, then perhaps a better way would be to directly get wallpapers using your terminal with the help of tools like curl or wget. Suppose you want to directly download a random "nature" wallpaper from the API, you can do it with curl:

$ curl -L -o background.jpg https://wallzy.onrender.com/nature

I also wrote a small bash script that can automatically fetch and set the wallpapers for you (only on GNOME desktops with Linux!). You can check the script on the GitHub page of the project.

# automatically download a tagged image and apply as wallpaper
$ ./wallzy.sh [tag]

End

Thanks a lot for reading this far! This was a nice little side project for me and I learnt some interesting things along the way like using FastAPI, sqlmodel, deploying code on Render among others. It was a fun learning experience and I hope you were also able to find some cool new wallpapers for your desktop!