<p>By the end of this tutorial, you will have a comprehensive understanding of building RESTful APIs using FastAPI, containerizing the application with Docker, and deploying it to a managed Kubernetes cluster on Civo.</p>
<h2>What are RESTful APIs?</h2>
<p>RESTful API (Representational State Transfer API) is an architectural style and set of principles for designing networked applications. It is a popular approach for creating web services that can be consumed by client applications over the internet.</p>
<p>RESTful APIs allow access and manipulation of data on a remote server using HTTP verbs like GET, POST, PUT, and DELETE. RESTful APIs are versatile and can be used for web, mobile, and desktop applications.</p>RESTful APIs offer several key benefits:
<br><br>
<ol> <li><strong>Security:</strong> RESTful APIs employ various security measures to protect sensitive data and ensure secure communication. They often use standard security protocols such as HTTPS, which encrypts data transmitted over the network. Additionally, authentication and authorization mechanisms can be implemented, such as token-based authentication or OAuth, to ensure that only authorized users can access the API resources.</li> <li><strong>Simplicity:</strong> RESTful APIs follow a simple and uniform architectural style making them easy to understand and use. They use standard HTTP methods and status codes, which simplifies the development process. By adhering to well-defined conventions, developers can build APIs that are intuitive and consistent, leading to better adoption and reduced complexity.</li> <li><strong>Efficiency:</strong> RESTful APIs are designed to be lightweight and efficient, allowing for swift and responsive communication between clients and servers. With the utilization of caching, clients and intermediaries can store responses reducing the need for frequent server requests.</li> <li><strong>Scalability:</strong> RESTful APIs are inherently scalable, allowing applications to efficiently handle heavy traffic and increasing data volumes. By following the principles of REST, such as statelessness and resource-oriented design, APIs can be designed to scale horizontally through adding more servers or vertically by upgrading hardware resources. Furthermore, the use of load balancing techniques, caching mechanisms, and efficient database design can further enhance the scalability of RESTful APIs.</li> </ol>
<img src="https://civo-com-assets.ams3.digitaloceanspaces.com/content_images/2398.blog.png?1689169437" alt="RESTful API">
<h2>What is FastAPI?</h2>
<p><a href="https://github.com/tiangolo/fastapi" target="_blank" rel="noopener">FastAPI</a> is a modern, high-performance web framework for building APIs with Python. It provides several advantages:</p>
<ul> <li>High performance and scalability</li> <li>Easy-to-use and intuitive API definition</li> <li>Automatic documentation generation</li> <li>Leverages Python-type annotations for improved error catching and code quality</li> <li>Integration with <a href="https://github.com/pydantic/pydantic" target="_blank" rel="noopener">Pydantic</a> for powerful data modeling</li> </ul>
<p>It is also important to consider the following disadvantages when using FastAPI:</p> <ul> <li>FastAPI is a relatively new framework, released on December 5, 2018, so there may be fewer resources available.</li> <li>Not as well-known as some other Python web frameworks</li> </ul>
<h2>Prerequisites</h2>
<p>Before we start, there are some prerequisites you’ll need to have in place:</p>
<ul>
<li><strong><a href="https://www.python.org/downloads/" target="_blank" rel="noopener">Python 3.7+ installed on your local machine</a>:</strong> You can check the Python version by running the command python --version
in your terminal.</li>
<li><strong><a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener">Docker installed</a>:</strong> Docker is used to containerize the application and can be installed by following the official documentation.</li>
<li><strong><a href="https://www.civo.com/docs/account/signing-up" target="_blank" rel="noopener">Civo account</a>:</strong> We will be using Civo in this tutorial as it provides a managed Kubernetes cluster for deploying our application.</li>
<li><strong><a href="https://www.civo.com/docs/kubernetes/create-a-cluster" target="_blank" rel="noopener">Launched cluster on Civo</a>:</strong> Download the credentials to access the cluster.</li>
<li><strong><a href="https://docs.python.org/3/tutorial/" target="_blank" rel="noopener">Basic understanding of Python</a>:</strong> Familiarity with the Python programming language is required to understand and follow along with the tutorial.</li>
</ul>
<h2>Setting up the Development Environment</h2>
<p>To set up your development environment, you first need to open a terminal. Next, follow these steps:</p>
<strong>Step 1:</strong> Verify if you have virtualenv installed by running the following command:
<pre><code class="bash language-bash">pip install virtualenv </code></pre>
<strong>Step 2:</strong> Create and activate a virtual environment:
<pre><code class="bash language-bash">virtualenv venv source venv/bin/activate </code></pre>
<i>Note: If you're using Windows, use the <code>venv\Scripts\activate</code> command instead.</i>
<strong>Step 3:</strong> Create a project directory for the FastAPI application:
<pre><code class="bash language-bash">mkdir fastapiproject cd fastapiproject </code></pre>
<strong>Step 4:</strong> Install the required packages - Create a <code>requirements.txt</code> file and add the contents below:
<pre><code class="bash language-bash">fastapi==0.95.1 uvicorn==0.22.0 SQLAlchemy==2.0.15 </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>uvicorn</code>: is a lightning-fast ASGI (Asynchronous Server Gateway Interface) server that provides the runtime environment for running our FastAPI application. It is highly efficient and allows our application to handle multiple concurrent requests. <br><br> <code>sqlalchemy</code>: SQLAlchemy is a powerful SQL toolkit and Object-Relational Mapping (ORM) library. It provides a set of high-level APIs for interacting with databases, allowing us to define database models, perform database operations, and write efficient database queries. </blockquote>
<strong>Step 5:</strong> Run the command below to install the packages:
<pre><code class="bash language-bash">pip install -r requirements.txt </code></pre>
To test the setup, create a <code>main.py</code> file in the project directory with the following code:
<pre><code class="bash language-python">from fastapi import FastAPI
app = FastAPI()
@app.get('/hello') def hello(): return 'Hello, world!' </code></pre>
<strong>Step 6:</strong> Open your terminal and start the server using the following command:
<pre><code class="bash language-bash">uvicorn main:app --reload </code></pre>
Access the automatically generated documentation by opening your browser and visiting the following URL: <code>http://127.0.0.1:8000/docs</code>
<h2>Designing the RESTful API with FastAPI</h2>
With the development environment set up, we can create the RESTful API. We will be creating a RESTful API for a <code>Book</code> model, which will involve implementing CRUD (Create, Read, Update, Delete) operations.
<strong>Step 1:</strong> Create a <code>database.py</code> file in the project directory and add the following contents:
<pre><code class="bash language-python">from sqlalchemy import createengine from sqlalchemy.ext.declarative import declarativebase from sqlalchemy.orm import sessionmaker
SQLALCHEMYDATABASEURL = "sqlite:///./app.db"
engine = createengine(SQLALCHEMYDATABASEURL, connectargs={"checksamethread":False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>database.py</code> file: Sets up the database connection using SQLAlchemy. In this section, we use SQLite, but we will switch to a more robust database like PostgreSQL during deployment. </blockquote>
<strong>Step 2:</strong> Create a <code>models.py</code> file and add the following contents:
<pre><code class="bash language-python">from database import Base from sqlalchemy import Column, String, Float, Date, Integer
class Book(Base): tablename= 'books' id = Column(Integer, primarykey=True, index=True) title = Column(String(100), nullable=False) author = Column(String(100), nullable=False) price = Column(Float, nullable=False) publicationdate = Column(Date, nullable=False) </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>Book</code> class: Represents the database model for a book in our API. It includes columns for the book's title, author, price, and publication date. </blockquote>
<strong>Step 3:</strong> Create a <code>schemas.py</code> file and add the following contents:
<pre><code class="bash language-python">from pydantic import BaseModel from datetime import date
class BookBase(BaseModel): author: str title: str price: float publication_date: date
class BookCreateUpdate(BookBase): pass
class BookDetail(BookBase): id: int
class Config:
orm_mode = True
</code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>schemas.py</code>: Defines the Pydantic models for data validation and includes a <code>BookBase</code> model that represents the base fields of a book. <br><br> <code>BookCreateUpdate</code> model: Inherits from <code>BookBase</code> and represents the data for creating and updating books. <br><br> <code>BookDetail</code> model: Inherits from <code>BookBase</code> and includes the book's ID. The <code>Config</code> class with <code>orm_mode=True</code> enables SQLAlchemy integration to convert database models to Pydantic models. </blockquote>
<strong>Step 4:</strong> Create a <code>crud.py</code> file and add the following contents:
<pre><code class="bash language-python">from sqlalchemy.orm import Session from models import Book from schemas import BookDetail, BookCreateUpdate from fastapi import HTTPException
def booknotfound(): raise HTTPException(detail='Book not found', status_code=404)
def get_books(db: Session): return db.query(Book).all()
def createbook(db: Session, book: BookCreateUpdate): newbook = Book(**book.dict()) db.add(newbook) db.commit() db.refresh(newbook) return new_book
def updatebook(db: Session, book: BookCreateUpdate, bookid:int): dbbook = db.query(Book).filter(Book.id == bookid).first() if not dbbook: booknotfound() for field, value in book.dict().items(): setattr(dbbook, field, value) db.commit() db.refresh(dbbook) return dbbook
def deletebook(db: Session, bookid: int): delete=db.query(Book).filter(Book.id == bookid).delete() if not delete: booknot_found() db.commit()
def getbook(db: Session, bookid: int): book= db.query(Book).filter(Book.id == bookid).first() if not book: booknot_found() return book </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>booknotfound</code>: Raises an HTTPException with a 404 status code if a book is not found in the database. <br><br> <code>getbooks</code>: Retrieves all books from the database. <br><br> <code>createbook</code>: Creates a new book in the database. </blockquote> <blockquote style="font-family: inherit; font-size: inherit;"> <code>updatebook</code>: Updates an existing book in the database. <br><br> <code>deletebook</code>: Deletes a book from the database. <br><br> <code>get_book</code>: Retrieves a single book from the database. </blockquote>
<strong>Step 5:</strong> Finally, update the <code>main.py</code> file with the following code:
<pre><code class="bash language-python">from fastapi import FastAPI, Depends from sqlalchemy.orm import Session from database import SessionLocal, engine import models import crud import schemas
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db(): db = SessionLocal() try: yield db finally: db.close()
@app.post('/books/', responsemodel=BookDetail) def createbook(book: schemas.BookCreateUpdate, db: Session = Depends(getdb)): return crud.createbook(db, book)
@app.get('/books/' ,responsemodel=list[BookDetail]) def getbooks(db: Session = Depends(getdb)): return crud.getbooks(db)
@app.get('/books/{bookid}', responsemodel=BookDetail) def deletebook(bookid: int, db: Session = Depends(getdb)): return crud.getbook(db, book_id)
@app.put('/books/', responsemodel=BookDetail) def updatebook(book: schemas.BookCreateUpdate, db: Session = Depends(getdb)): return crud.updatebook(db, book)
@app.delete('/books/{bookid}') def deletebook(bookid: int, db: Session = Depends(getdb)): crud.deletebook(db, bookid) return {'message': 'Book deleted'} </code></pre>
The code above adds the endpoints for the API, including creating, reading, updating, and deleting books.
<blockquote style="font-family: inherit; font-size: inherit;"> The <code>getdb</code> function is a dependency that provides a database session to each endpoint that depends on it. <br><br> The <code>responsemodel</code> parameter in the functions specifies the Pydantic models to use for the response. </blockquote>
You can now test the API by running the server using the command <code>uvicorn main:app</code> and accessing the documentation at <code>http://localhost:8000/docs</code>
<h2>Containerizing the Application</h2>
Containerization has become popular for its ease of deployment and distribution. Docker is one of the most widely used containerization tools that enable us to containerize our applications efficiently.
Follow these steps to containerize the application:
<strong>Step 1:</strong> Ensure that Docker is installed on your system. If not, refer to the <a href="https://www.civo.com/learn/build-deploy-restfulapi-fastapi-kubernetes#prerequisite">prerequisites</a> and install it.
<strong>Step 2:</strong> Create a <code>Dockerfile</code> in the project directory and include the following contents:
<pre><code class="bash language-docker">FROM python:3.9-alpine
WORKDIR /app
COPY ./requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
</code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> The <code>Dockerfile</code> starts with the base image of Python 3.9-alpine. <br><br> <code>WORKDIR</code> command: Sets the working directory inside the container as <code>/app</code>, which will be the root directory for our application. <br><br> <code>COPY</code> command: Copies the <code>requirements.txt</code> file from the local directory to the container's <code>/app</code> directory. <br><br> <code>RUN</code> command: Installs the Python dependencies specified in <code>requirements.txt</code> using <code>pip</code>. <br><br> The second <code>COPY</code> command: Copies the entire project directory (including the application code) into the container's <code>/app</code> directory. <br><br> <code>CMD</code> command: Specifies the command to run when the container starts. In this case, it runs the Uvicorn server with the FastAPI application, listening on <code>0.0.0.0:8000</code>. </blockquote>
<strong>Step 3:</strong> Start the Docker service if it is not already running:
<pre><code class="bash language-bash">sudo service docker start </code></pre>
<strong>Step 4:</strong> Build the Docker image by running the following command in the terminal:
<pre><code class="bash language-bash">docker build -t fastapi-app . </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>-t</code>: Specifies the tag for the image. <br><br> The command builds the Docker image with the name <code>fastapi-app</code> (you can replace it with the name of your choice). <br><br> <code>.</code>: Specifies the current directory where the <code>Dockerfile</code> is located. </blockquote>
<strong>Step 5:</strong> Run the Docker container using the following command:
<pre><code class="bash language-bash">docker run -p 80:8000 fastapi-app </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> <code>-p</code>: Maps the host's port 80 to the container's port 8000. <br><br> <code>fastapi-app</code>: Specifies the image to run. </blockquote>
<strong>Step 6:</strong> Access the API documentation by opening your browser and navigating to <code>http://127.0.0.1/docs</code>.
Before deploying the containerized application, some changes need to be made to the project to support the use of a PostgreSQL database during deployment.
<strong>Step 7:</strong> Edit the <code>database.py</code> file with the following contents:
<pre><code class="bash language-python">from sqlalchemy import createengine from sqlalchemy.ext.declarative import declarativebase from sqlalchemy.orm import sessionmaker import os
DATABASEURL = os.getenv('POSTGRESQLDATABASE_URL')
engine = createengine( DATABASEURL )
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() </code></pre>
The <code>database.py</code> file has been modified to enable the use of a PostgreSQL database during deployment, by passing the database URL as an environment variable
<strong>Step 8:</strong> Next, edit the <code>requirements.txt</code> file to include the following dependencies:
<pre><code class="bash language-bash">fastapi==0.95.1 uvicorn==0.22.0 SQLAlchemy==2.0.15 psycopg2-binary==2.9.6 </code></pre>
<blockquote style="font-family: inherit; font-size: inherit;"> The <code>psycopg2-binary</code> package has been added to enable the PostgreSQL database connection. </blockquote>
<strong>Step 9:</strong> After making these changes, rebuild the Docker image using the following command:
<pre><code class="bash language-bash">docker build . -t fastapi-app </code></pre>
<strong>Step 10:</strong> Once the image is built, you can push it to a Docker repository. Here are the steps to push the image to Docker Hub:
<ol> <li>Sign up at <a href="https://hub.docker.com/signup" target="_blank" rel="noopener">Docker Hub</a></li> <li>Log in to the Docker repository using the following command and provide your credentials when prompted:
<pre><code class="bash language-bash">docker login </code></pre></li>
<li>Tag the image with your Docker Hub username, repository name, and tag. Replace <code>your-username</code> and <code>your-repository-name</code> with the appropriate values:
<pre><code class="bash language-bash">docker tag fastapi-app your-username/your-repositry-name:fastspi-app </code></pre></li>
<li>Push the repository to the container registry using the following command:
<pre><code class="bash language-bash">docker push your-username/your-repositry-name:fastspi-app </code></pre></li></ol>
<strong>Step 11:</strong> Finally, before proceeding with deployment, you need to provision a PostgreSQL database on Civo:
<ol> <li><a href="https://dashboard.civo.com/signup" target="_blank" rel="noopener">Sign in</a> to your Civo account. <li>Create a new PostgreSQL instance with the desired specifications, such as CPU and RAM.</li> <li>Make a note of the connection details, including the hostname, port, database name, user, and password.</li> </ol>
<h2>Deploying the FastAPI Application on Civo</h2>
To deploy the FastAPI application to a managed Kubernetes cluster on Civo, follow these steps:
<strong>Step 1:</strong> Ensure that you have <a href="https://www.civo.com/docs/kubernetes/create-a-cluster" target="_blank" rel="noopener">launched a cluster on Civo</a>, your downloaded cluster kubeconfig credential, and installed <a href="https://kubernetes.io/docs/tasks/tools/#kubectl" target="_blank" rel="noopener">kubectl</a> on your local machine.
<strong>Step 2:</strong> Configure <code>kubectl</code> with the downloaded cluster credentials by executing the following commands:
<pre><code class="bash language-bash">export KUBECONFIG=/path/to/downloaded/kubeconfig/credential </code></pre>
<i>Note: Replace <code>/path/to/downloaded/kubeconfig/credential</code> with the path to your downloaded kubeconfig cluster credential.</i></li>
<strong>Step 3:</strong> Create a <code>.env</code> file in your project directory with the following contents:
<pre><code>POSTGRESQLDATABASEURL=postgresql://{your-db-username}:{your-db-password}@{your-db-host>:{your-db-port}/{your-db-name} </code></pre>
<i>Note: Replace all the details with the information you saved when creating your managed PostgreSQL database on Civo.</i>
<strong>Step 4:</strong> Run the following command to create a Kubernetes secret for the environment variables:
<pre><code>kubectl create secret generic --from-env-file .env fastapi-secret </code></pre>
This command creates a secret named <code>fastapi-secret</code> from the <code>.env</code> file. Kubernetes secrets are used to store sensitive information such as credentials securely. However, it is important to note that by default, Kubernetes stores secrets in an unencrypted format. For more detailed information, refer to the official Kubernetes <a href="https://kubernetes.io/docs/concepts/configuration/secret/" target="_blank" rel="noopener">documentation</a>.
<strong>Step 5:</strong> Create an <code>api-deployment.yaml</code> file with the following contents:
<pre><code class="bash language-yaml"> apiVersion: apps/v1 kind: Deployment metadata: name: fastapi-app spec: replicas: 2 selector: matchLabels: app: fastapi type: api template: metadata: labels: app: fastapi type: api spec: containers: - name: fastapi-app image: your-username/your-repository-name:fastapi-app ports: - containerPort: 8000 envFrom: - secretRef: name: fastapi-secret </code></pre>
Let's break down what each section of the <code>api-deployment.yaml</code> file does:
<blockquote style="font-family: inherit; font-size: inherit;"> <code>apiVersion: apps/v1</code>: Specifies the Kubernetes API version we are using for the deployment definition. <br><br> <code>kind: Deployment</code>: Defines that we are creating a deployment object. <br><br> <code>metadata: name: fastapi-app</code>: Sets the name of the deployment to <code>fastapi-app</code>. This name will be used to identify and reference the deployment within the Kubernetes cluster. <br><br> <code>spec: replicas: 2</code>: Specifies that we want to have two replicas of our FastAPI application running. This means that the deployment will create and manage only two instances of the application. <br><br> <code>selector: matchLabels: app: fastapi type: api</code>: Defines the labels used to select the pods controlled by this deployment. The deployment will include pods that have both the <code>app=fastapi</code> and <code>type=api</code> labels. <br><br> <code>template: metadata: labels: app: fastapi type: api</code>: Specifies the labels to be applied to the pods created by the deployment. The labels <code>app=fastapi</code> and <code>type=api</code> will be assigned to the pods. <br><br> <code>spec: containers: - name: fastapi-app image: your-username/your-repository-name:fastapi-app</code>: Defines the container configuration for the deployment. It specifies the name of the container as <code>fastapi-app</code> and the image to be used from your Docker repository (replace <code>your-username</code> and <code>your-repository-name</code> accordingly). <br><br> <code>ports: - port: 8000</code>: Exposes port 8000 of the container for communication. This is the port on which the FastAPI application is running. <br><br> <code>envFrom: - secretRef: name: fastapi-secret</code>: Fetches the environment variables (<code>POSTGRESQLDATABASEURL</code>) from the <code>fastapi-secret</code> secret. These variables provide the necessary database connection details for our FastAPI application. </blockquote>
<strong>Step 6:</strong> Create the deployment by running the following command:
<pre><code class="bash language-bash">kubectl apply -f api-deployment.yaml </code></pre>
This command deploys the application to the Kubernetes cluster based on the configuration in the <code>api-deployment.yaml</code> file.
<strong>Step 7:</strong> Create an <code>api-service.yaml</code> file with the following contents:
<pre><code class="bash language-yaml"> apiVersion: v1 kind: Service metadata: name: fastapi-app spec: type: LoadBalancer ports: - protocol: TCP port: 80 targetPort: 8000 selector: app: fastapi type: api </code></pre>
Let's break down each section of the <code>api-service.yaml</code> file:
<blockquote style="font-family: inherit; font-size: inherit;"> <code>apiVersion: v1</code>: Specifies the Kubernetes API version we are using for the deployment definition. <br><br> <code>kind: Service</code>: Defines that we are creating a service object. <br><br> <code>metadata: name: fastapi-app</code>: Sets the name of the service to <code>fastapi-app</code>. This name will be used to identify and reference the service within the Kubernetes cluster. <br><br> <code>spec: type: LoadBalancer</code>: Specifies the type of service as a LoadBalancer. This provisions a <a href="https://www.civo.com/docs/kubernetes/load-balancers" target="_blank" rel="noopener">load balancer on Civo</a> with an external IP address, allowing external traffic to reach our FastAPI application. <br><br> <code>ports: - protocol: TCP port: 80 targetPort: 8000</code>: Defines the port configuration for the service. It exposes port 80 of the service and directs the incoming traffic to the container's port 8000, where our FastAPI application is running. <br><br> <code>selector: app: fastapi type: api</code>: Specifies the labels used to select the pods that should be part of the service. The <code>app: fastapi</code> and <code>type : api</code> label ensures that only pods with the labels <code>app=fastapi</code> and <code>type=api</code> are included in the service. </blockquote>
<strong>Step 8:</strong> Create the service by running the following command:
<pre><code class="bash language-bash">kubectl apply -f api-service.yaml </code></pre>
This command creates a service in the Kubernetes cluster based on the configuration in the <code>api-service.yaml</code> file.
<strong>Step 9:</strong> Get the external IP of the service by running the following command:
<pre><code class="bash language-bash">kubectl get service fastapi-app </code></pre>
This command retrieves information about the deployed service, including the external IP.
Once you have the external IP, you can access the API by opening your browser and navigating to <code>http://(external-ip-address)/docs</code>. The API documentation should be accessible.
<h2>Summary</h2>
This tutorial has provided you with a comprehensive guide on how to build and deploy a RESTful API using FastAPI to a Managed Kubernetes Cluster on Civo. Throughout the tutorial, we have covered all the necessary steps, from setting up the development environment to containerizing the application and deploying it to a Managed Kubernetes Cluster on Civo.
<h3>Further resources</h3>
<p>To further enhance your skills and expand your knowledge, consider exploring the following resources:</p>
<ul> <li><a href="https://fastapi.tiangolo.com/" target="_blank" rel="noopener">FastAPI Official Documentation</a></li> <li><a href="https://kubernetes.io/docs/home/" target="_blank" rel="noopener">Kubernetes Documentation</a></li> <li><a href="https://docs.docker.com/" target="_blank" rel="noopener">Docker Documentation</a></li> <li><a href="https://www.civo.com/docs" target="_blank" rel="noopener">Civo Documentation</a></li> </ul>