Deploy FastAPI Serverless-ly

Originally published at https://amalshaji.com/deploy-fastapi-serverless-ly/

In this post, we look at how to deploy a FastAPI application on a serverless platform. The platform that we deploy to is vercel. Vercel provides a generous free tier to host our application. The application we build for this post is a GitHub view counter, which counts the number of page visits.

What is serverless?

Serverless is not server-less. It is a kind of platform where the developer worries less about the deployment. Serverless allows you to spend all the time on the development of your application. When it comes to deployment, push, and the serverless platform takes care of the rest.

Benefits of serverless
  • No server management - The application runs on the server, but the developer need not worry about the server setup
  • Pay for what you use - Serverless platform charges for what you use, like CPU, function calls, etc. Some even calculate the price down to the millisecond.
  • Automatic Scaling - They scale up and down according to demand
  • Quick deployment - Push the repository/workspace to deploy. Serverless often deploys in quick time, and this makes it easy to ship updates.
  • Low latency - The code can run closer to the user.
Downsides
  • Testing is a pain - It isn't easy to replicate the serverless environment.
  • Short-running process - The free tier of vercel only provides 10 second per function call(a request-response cycle). This is convenient for some tasks, this is convenient, but you cannot run any background tasks that run long.
  • Cold start - If functions are not called for some time, the serverless code goes to sleep to save memory and CPU usage. When a request is made, the code wakes up(cold start). This creates performance issues because of the slow response. If the serverless code is consistently running, there won't be any performance issues.

GitHub view counter

A GitHub view counter is a simple application that counts the number of times a page is viewed. VC became popular after GitHub introduced profile READMEs.

The counter counts when a request is sent to an endpoint. It responds by sending an SVG that contains the count. Images/SVG are the only way to fetch external sources into a markdown flavored README file.

Since these assets are fetched over insecure http call, GitHub proxies all <img> calls to a server running on heroku, now all the requests come from one source, the proxy server. This makes it very difficult to count the unique users, so we count the number of times the resource was fetched.

Building our application

Installing requirements
pip install fastapi

We don't need a server here, as serverless will take care of it.

Getting a custom SVG(badge)

We need to get an SVG to send back to the user. You should see a custom badge form to navigate to https://shields.io and scroll down. Type in the labels as Profile Views and some random value for the label 26.

badge_form.PNG

Now click generate. This should generate a badge in a new tab. Now right click and view page source to get the HTML template for the badge. Create a lambda function to take in a variable count and replace all instances of 26 with {count}(use f-strings).

SVG_STRING = (
    lambda count: f"""
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="104" height="20" role="img" aria-label="Profile Views: {count}">
    <title>Profile Views: 
        {count}
    </title>
    <linearGradient id="s" x2="0" y2="100%">
        <stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/>
    </linearGradient>
    <clipPath id="r">
        <rect width="104" height="20" rx="3" fill="#fff"/>
    </clipPath>
    <g clip-path="url(#r)">
        <rect width="81" height="20" fill="#555"/>
        <rect x="81" width="23" height="20" fill="#97ca00"/>
        <rect width="104" height="20" fill="url(#s)"/>
    </g>
    <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
        <text aria-hidden="true" x="415" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="710">
            Profile Views
        </text>
        <text x="415" y="140" transform="scale(.1)" fill="#fff" textLength="710">
            Profile Views
        </text>
        <text aria-hidden="true" x="915" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="130">
            {count}
        </text>
        <text x="915" y="140" transform="scale(.1)" fill="#fff" textLength="130">
            {count}
        </text>
    </g>
</svg>
"""
)

I used a simple dict for the database. But when the serverless created multiple instances of the functions, it created multiple copies of the dict with different values—this lead to returning different values on each request. Using an external database works best. We'll use Redis. Head over to https://redislabs.com/ and create a free account. Login and choose Redis Enterprise Cloud. Create a new subscription on any cloud service(AWS, GCP, Azure) by choosing the free plan(30MB). Once the subscription is created, copy the endpoint and password and store them in a secure location.

Install Redis for python.

pip install redis

Create a Redis connection in app.py

db = Redis(
    host=getenv("REDIS_ENDPOINT"),
    port=getenv("REDIS_PORT"),
    password=getenv("REDIS_PASSWORD"),
)

Now we define the counter endpoint as,

@app.get("/api/")
def user_count(username: str = "amalshaji", title: str = "Profile"):
    title = title.capitalize()
    count = db.get(username)
    if count is not None:
        db.incr(username, amount=1)
        count = count.decode()
        return Response(
            content=SVG_STRING(title, count + 1), media_type="image/svg+xml"
        )
    db.set(username, 1)
    return Response(content=SVG_STRING(title, 1), media_type="image/svg+xml")

The route /api takes two parameters, username and title. The username defaults to amalshaji, and the title defaults to Profile. You can use a random id as username and title as Project to track a particular project's view count.

Deploying to vercel

Create an account on vercel.

After creating the account, install vercel-cli and login

npm i -g vercel
vercel login

Now create a vercel.json in the project directory to setup vercel configuration.

// vercel.json

{
    "version": 2,
    "builds": [
        {
            "src": "app.py",
            "use": "@now/python"
        }
    ],
    "routes": [
        {
            "src": "(.*)",
            "dest": "app.py"
        }
    ]
}

Vercel lets you create a serverless environment using the vercel dev command. In my case, it was always failing, even though the code was deployed successfully.

Before running the application, we need to set up environment variables. Navigate to the project dashboard(in my case vercel.com/amalshaji/pvc). Click on the Settings tab and choose Environment Variables. Set the three environment variables, REDIS_ENDPOINT, REDIS_PORT, and REDIS_PASSWORD.

Redis port is not provided separately. The endpoint is in the format URL: PORT.

Vercel automatically encrypts all environment variables.

envs.PNG

Once this is done, deploy the preview app by running vercel. Once the deployment is finished, it'll provide a preview link.

preview.PNG

Navigate to the preview link and test the application. Once satisfied, push the preview deployment to production using vercel --prod

prod.PNG

That's it. The profile view counter is successfully running, in my case, at https://pvc.vercel.app.

Demo

CodeResponse
![](https://pvc.vercel.app/api/?username=amalshaji)
![](https://pvc.vercel.app/api/?username=xsdf434&title=project)

This counter can be faked. There is no point in faking the numbers by spamming. A billion views mean the counter hit 1 billion times.

Instructions for self-hosting will be available on the GitHub repo. You can tweak to fit your use case.

GitHub: amalshaji/pvc

Read about vercel limits on free tier here

If you have setup Redis or any other database, you can directly deploy by clicking the button below,

Deploy with Vercel

No Comments Yet