Deploy a Python API on Vercel Using Postgres

Article Image

APIs have changed how software is built in recent years, allowing for more reusable code that can interact with any software development tool. Modern APIs have gained defined standards (usually HTTP and REST) that are developer-friendly, easily available, and widely understood, making it easier for developers to build maintainable code with security checks in place, as well as comprehensive documentation.

FastAPI is a high-performing Python web framework for creating APIs with standard Python-type hints, allowing you to easily create speedy, intuitive, and robust web applications with fewer bugs. In addition, it has built-in support for API documentation, powered by Swagger.

In this tutorial, we’ll learn how to build and deploy a Postgres FastAPI application on Vercel by creating a simple task manager application. To follow along, ensure you clone the Github repository for this project. Let’s get started!.

Prerequisites

This tutorial is a hands-on demonstration. To follow along, ensure you have the following installed:

What Is Vercel?

Vercel is a cloud hosting platform, widely regarded as the best place to deploy any frontend application. Vercel provides the flexibility of zero-configuration deployment to its global edge network, enabling dynamic application scalability without breaking a sweat.

Vercel combines the most remarkable development experience with obsessive attention to end-user efficiency, as well as a slew of exciting features like:

  • Fast refresh: a dependable live-editing experience for your UI components.
  • Versatile data fetching: Connect your pages to any data source, headless CMS, or API to ensure that they work in everyone’s development environment.
  • Localhost perfection: All your cloud primitives, from caching to serverless functions, run flawlessly on localhost.

Setting up Our Project

Before we dive too deep, let’s create our project structure and install the dependencies needed for our application. We’ll start by creating the project folder. Open your terminal and run the following commands:

mkdir PostgresWithFastAPI && cd PostgresWithFastAPI
touch {main,database,model,schema,session}.py

After running the command above and creating a virtual environment for our project (which we’ll cover in the next section), our project structure will look like this:

Our project structure

We’ll be working with each of these files throughout this tutorial.

Creating Virtual Environment

It is always good to create a virtual environment for the Python projects you build. A virtual environment will contain your dependencies for the project and isolate them, keeping your project neatly contained. We’ll create a virtual environment for this project by using virtualenv:

Now, create and activate your virtual environment by running the commands below:

python3 -m venv env
source env/bin/activate

We have successfully created a virtual environment for the project. We’ll also need to install Fastapi, Uvicorn, Sqlalchemy, and psycopg2-binary with the command below:

pip install fastapi uvicorn sqlalchemy psycopg2-binary

Now run the command below to save our dependence in a requirements.txt file.

pip freeze > requirements.txt

Excellent. Now, let’s go ahead and create our FastAPI server.

Creating a FastAPI Server

With our project set up, we can now create our FastAPI server. First, open the main.py file in the project’s root directory and add the following code to it:

from fastapi import FastAPI
app = FastAPI()

@app.get("https://dzone.com/")
def read_root():
    return {"message": "Server is up and running!"}

Next, navigate to the project root directory on your terminal, and test out the server by running the command below:

uvicorn main:app --reload

The –reload flag we added to the command tells FastAPI to watch for updates on our codebase, and reload the server if it finds one. Now, make a Get request to the server to ensure everything is working with Postman.

Making a Get request to the server

Our server has successfully been created and running. Next, we’ll need a database to save our user’s records. Let’s go ahead and set up one.

Setup Postgres Database

We can now set up our Postgres database to store our user’s records with our server setup. We’ll use SQLAlchemy ORM (Object Relational Mapper) to connect our database with our application. To begin, we’ll need to create a database with the following steps. First, switch to the system’s Postgres user account.

Then, create a new user account. You can then proceed by following the screenshot below:

Setting up our Postgres database

Next, create a new database. You can do that with the following command:

Now, we’ll connect to the database we just created. Open the database.py file, and add the following code snippet below:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:1234@localhost:5432/task"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

In the above code snippet, a connection is made to our database using the create_engine class we import from SqlAlchemy. We also created a Sessionlocal instance from the sessionmaker class. We disabled autocommit and autoflush, then bound the database engine to the session. Finally, we created a Base instance from the declarative_base class, which we’ll use to create our application’s database model, and our database connection.

Creating Our Database Model

With our Postgres database setup, let’s define how the user’s data will be stored by creating a model. Open the model.py file and add the following code snippet to it.

from sqlalchemy.schema import Column
from sqlalchemy.types import String, Integer, Text
from database import Base

class Task(Base):
   __tablename__ = "Tasks"
   id = Column(Integer, primary_key=True, index=True)
   task_name = Column(String(20))
   task_des = Column(Text())
   created_by = Column(String(20))
   date_created = Column(String(15))

In the above code snippet, we defined our data models by importing Column and passing the data types we expect to store for each of the fields in our database ( Integer , String(), and Text()).

We also imported the Base instance we created in our database.py file which we used to create our base model class. Then we set our table name( Tasks) using the __tablename__ attribute. To create a distinction between the data stored in our database table, we added the primary_key and index parameters to our id field and set them to true.

Creating Our Database Schema

Let’s define a schema for our application. We need to define a Pydantic schema that will read data and return it from the API. Open the Schema.py file and add the following code snippet to it:

from pydantic import BaseModel
from typing import Optional

class task_schema(BaseModel):
   task_name :str
   task_des :str
   created_by : Optional[str]= None
   date_created : Optional[str]= None

   class Config:
       orm_mode = True

In the above code snippet, we defined our model validations, which ensures the data coming from the client-side is the same data type as the field we defined. We will be expecting a string value for our task_name and task_des fields, and optional string values for the created_by and date_created fields. The subclass config with orm_mode set to True will instruct the Pydantic model to read the data as a dictionary and attribute.

Creating Our App Routes

With our schema created, let’s define the routes of our application. To begin, open the session.py file and create a create_get_session() function to create and close the session our routes with the code snippet below:

import model
from database import SessionLocal, engine

model.Base.metadata.create_all(bind=engine)
def create_get_session():
   try:
       db = SessionLocal()
       yield db
   finally:
       db.close()

In the above code snippet, we created our table using the field we defined in our model by calling the model.Base.metadata.create_all() function and binding it to our database engine.

Then, open the main.py file and import all our modules with the code snippet below:

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from model import Task
from schema import task_schema
from session import get_database_session
…

Next, create the read_tasks routes with the code snippet below:

…
@app.get("/task", response_model=List[task_schema], status_code=200)
async def read_tasks(db: Session = Depends(create_get_session)):
   tasks = db.query(Task).all()
   return tasks
…

In the above code snippet, we created a read_tasks route which will listen to a GET request. We passed in our response model, which returns a list of all the tasks in our database and a status code of 200 ( OK). In our read_tasks function, referenced our model session which will enable us to execute queries in our database.

Next, create a create_task route, to add new tasks to our database with the code snippet below:

…
@app.post('/task', response_model = task_schema, status_code=201)
async def create_task(task: task_schema, db: Session = Depends(create_get_session)):
   new_task = Task(
        task_name = task.task_name,
        task_des = task.task_des,
        created_by =task.created_by,
        datecreated = task.date_created,
   )
   db.add(new_task)
   db.commit()

   return new_task
…

In the code snippet above, we created a create_task route, which will listen to POST requests. This time our response model will return the task that was just created with a status code of 201(Created). Then we get the data from the request body by passing an item and assigning it to our Pydantic model. Also, we created a new_task object from our model class and passed in the data from our request body to the fields in our model. Then we add the new_task object to our database session, commit them and return the created object.

Next, we create the get_task route, which will return a task whose id is specified in the request parameter with the code snippet below:

…
@app.get("/task/{id}", response_model = task_schema, status_code=200)
async def get_task(id:int,db: Session = Depends(create_get_session)):
   task = db.query(Task).get(id)
   return task
…

In the above code snippet, we created our get_task route which will also listen to a GET request. But this time, we passed in the id of the task as a parameter in our endpoint. Our response model will return an item object with a status code of 200(OK). Then we query our database model ( Tasks) to get the task whose id is specified in the request parameter and return it to the users.

Next, we’ll create our update_task route, which will listen to a PATCH request, we also pass in our response model, which will return the task object that was updated with a status code of 200 (OK). Then we query our model for the item whose id is specified in the request parameter, reset the values of the task, save it to the databases, refresh the database, and return the updated record object to the user.

…
@app.patch("/task/{id}", response_model = task_schema, status_code=200)
async def update_task(id:int, task:task_schema, db: Session = Depends(create_get_session)):
   db_task = db.query(Task).get(id)
   db_task.task_name = task.task_name
   db_task.task_des =  task.task_des
   db.commit()
   db.refresh(db_task)

   return db_task
…

Finally, we’ll create the delete_task route, which will listen to a DELETE request and delete the task whose id is specified in the request parameter and return a status code of 200 (OK). We’ll query our database for the item and raise an HTTPException error if the item does not exist in the database. Then pass in a status code of 404 (NOT FOUND), and return null.

@app.delete('/task/{id}', status_code=200)
async def delete_task(id:int, db: Session = Depends(create_get_session)):
   db_task = db.query(Task).get(id)
   if not db_task:
       raise HTTPException(status_code="404",detail="Task id does not exist")
 
   db.delete(db_task)
   db.commit()

   return None

With that, we’ve set up all our routes. Let’s get our application tested locally.

Testing the Application

With our application setup, let’s go ahead test it out with Postman. We’ll start with the Post route.

Testing the application with the Post route

Next, is the GET route. We’ll test it the same way.

Testing the application with the Get route

Next, the GET route by id. You should see the results shown below:

Testing the application with the Get route by ID

Next is the PATCH route. Again, the correct response will look like the screen below:

Testing the application with the Patch route

Finally, the DELETE route.

Testing the application with the Delete route

Everything is looking good. Now, let’s get our application deployed to the cloud on Vercel.

Deploy to Vercel

With our routes tested, our application is ready to be deployed to the cloud in Vercel. Before you deploy the app, provision a remote database on Heroku for the project here, and update the database connection string in the database.py file.

SQLALCHEMY_DATABASE_URL = “Remote connection string”

Then sign up for an account on Vercel, and install the Vercel CLI tool with the command below:

//Ubuntu
sudo npm install vercel

//Windows
npm install -g vercel

Once the installation is completed, login to the Vercel CLI with the command below:

The above command will prompt you for the auth account you wish to log in with. Make your choice, and hit the enter key. You’ll be redirected to a new tab in your browser, if you see a success message, then you successfully logged in to your account. That means you can now access your project from the terminal.

Next, you’ll need a configuration file to tell Vercel where to find your main project file. Create a vercel.json file in the parent directory and add the following JSON code snippet:

{
 "builds": [{ "src": "main.py", "use": "@vercel/python" }],
 "routes": [{ "src": "/(.*)", "dest": "main.py" }]
}

In the above code snippet, we created an object that indicates the path to our application’s main file. We also stated the package to be used when building our app in the routes object. We direct all routing to the main.py file.

Now, we’ll initialize Vercel with the command below:

The above command will prompt you to fill in your project details. Follow the prompt in the screenshot below to do that:

Follow the command prompt to fill project details

At this point, our project has successfully been deployed on Vercel. You can test it out from the link.

Visualize Our Data With Arctype

With our application deployed on Vercel, let’s visualize the records in our database by connecting our database to Arctype. To get started, ensure you’ve downloaded and installed Arctype on your machine. Launch Arctype and click on Postgres to create a connection. Click on this link if you need help.

Visualizing records in the database - snapshot 1

After choosing Postgres, add your database credentials and click the save button to connect to the database.

Visualizing records in the database - snapshot 2

We have connected to our remote database. Click on the task table to run some queries on our database on Arctype. You can perform CRUD operations directly to the database from the Arctype, resulting in outputs like the one shown below.

Visualizing records in the database - snapshot 3

Arctype is a powerful SQL client with advanced collaboration and data visualization tools. Be sure to play around and experiment to find the best ways to study your application’s data.

Conclusion

In this tutorial, you’ve learned how to build and deploy a Postgres FastAPI application on Vercel. We started with a brief overview of Vercel and FastAPI. Then we created a FastAPI server, set up a Postgres database, connected the application to Postgres, performed CRUD operations, and visualized data with Arctype.

Now that you have this knowledge, how do you intend to build your next FastAPI application? Perhaps you can learn more about FastAPI and Arctype from their website and use what you’ve learned as inspiration for your next project. Feel free to reach out on Twitter or share your progress and questions here in the comments below.

.

Leave a Comment