FastAPI is a modern and performant web framework for building APIs, a task that typically requires using a frontend tool to handle the client side. Based on Pydantic and Starlette, FastAPI includes server-side rendering features and type hints for Python ≥ v3.6.0, supporting both the client side and server side. Additionally, FastAPI includes the following features:
Unlike other Python frameworks, like Flask, FastAPI is integrated with SQLAlchemy, which supports database integrations with MySQL, PostgreSQL, SQLite, Oracle, and Microsoft SQL Server.
In this tutorial, we’ll explore server-side rendering with FastAPI by building a simple database for adding and removing movie titles. You can follow along by cloning the GitHub repository for this project. Let’s get started!
Let’s start by setting up our application. Our project uses the following structure:
┣ static ┃ ┣ css ┃ ┃ ┣ overview.css ┃ ┃ ┗ style.css ┃ ┗ js ┃ ┃ ┗ script.js ┣ templates ┃ ┣ index.html ┃ ┗ overview.html ┣ .gitignore ┣ database.py ┣ main.py ┣ model.py ┣ requirements.txt ┗ schema.py
In the static
directory, we’ll store static files. templates
is the directory for our HTML pages, and database.py
is a file for our database connections. The model.py
file is for our database models, and the schema.py
file is for our database schema.
It’s good practice to create isolated Python environments for your Python project. To ensure that you have virtualenv
installed, run the command below:
pip install virtualenv
Now, create a new directory called server-side-rendering-with-fastapi
. Navigate to it and use the command below to create a virtual environment:
python3 -m venv env
To activate the virtual environment we just created, run the command below:
source env/bin/activate
Now, let’s install the necessary packages for our project. We will use Uvicorn as our ASGI development server, Jinja2 as our template engine, and python-multipart to receive form fields from the client:
pip install fastapi uvicorn jinja2 python-multipart
With our project set up, we can create our FastAPI server. Create a main.py
file in the project’s root directory and add the following code to it:
from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"message": "welcome to FastAPI!"}
Then, run the server with the command below:
uvicorn main:app --reload
The --reload
flag tells Uvicorn to reload the server whenever new code is added to the application. Next, open your browser and navigate to http://127.0.0.1:8000
, where you’ll see a JSON response similar to the following message:
{"message": "welcome to FastAPI!"}
Now that we’ve set up our FastAPI server, let’s get started with the SQLAlchemy ORM (Object Relational Mapper) and create a database. Let’s install SQLAlchemy and MySQL Connector/Python:
pip install sqlalchemy mysql-connector-python
In your terminal, run the following command to create a database in your MySQL database:
//Login to MySQL mysql -u root -p //Create database named serversiderendering CREATE DATABASE serversiderendering;
In your project’s root directory, create a database.py
file. We’ll import SQLAlchemy create_engine
, declarative_base
, and sessionmaker
. We’re using MySQLConnector to connect to our database, so our connection string will look like the following code:
>DATABASE_URL = "mysql+mysqlconnector://root@localhost:3306/serversiderendering"
We can connect to our database using the create_engine
function we just imported from SQLAlchemy. We’ll also import the sessionmaker
function, which creates a session for eliminating security issues in our application.
However, the session will not be created until a Sessionlocal
class instance is created from the sessionmaker
. We’ll disable autocommit
and autoflush
, then bind the database engine to the session.
The declarative_base
class, which we’ll use to create our application’s database model, is also required for our database connection. Add the code below to database.py
:
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = "mysql+mysqlconnector://root@localhost:3306/serversiderendering" engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()
Let’s build a SQLAlchemy database model now that our application is connected to our MySQL database. To begin, create a model.py
file in your project’s root directory. Each attribute of our database is represented by a Column
in SQLAlchemy. We’ll import Column
and pass a SQLAlchemy class type, like Integer
, String
, Text
, or Boolean
as an argument defining the type in the database.
To create the SQLAlchemy models, we’ll import and use the Base
class from our database.py
file. Then, add the __tablename__
attribute to the Movie
class, telling SQLAlchemy what name to use in the database for our model.
To receive unique data, we add the unique
parameter to our name field, make ID the primary_key
, and index
it. Add the code below to model.py
:
from sqlalchemy.schema import Column from sqlalchemy.types import String, Integer, Text from database import Base class Movie(Base): __tablename__ = "Movie" id = Column(Integer, primary_key=True, index=True) name = Column(String(20), unique=True) desc = Column(Text()) type = Column(String(20)) url = Column(String(100)) rating = Column(Integer)
Now, our database model has been configured, but we still need to create a schema for our model, which will read data and return it from the API. To achieve this, we’ll create a Pydantic schema for our model.
First, we’ll define our model validations, ensuring that the data coming from the client side is the same data type as the field we defined. Next, Pydantic’s orm_mode
will instruct the Pydantic model to read the data as a dictionary and as an attribute.
Create a schema.py
file in your project’s root directory and paste the code below into it:
from datetime import date from pydantic import BaseModel class Movie(BaseModel): id = int name = str desc = str type = str url = str rating = str class Config: orm_mode = True
Now, let’s go back to our main.py
file and import the database, schema, SessionLocal
variable, database engine, and model:
import schema from database import SessionLocal, engine import model
Then, we’ll create our table by calling the model.Base.metadata.create_all()
function and bind our database engine to it:
model.Base.metadata.create_all(bind=engine)
Finally, we’ll create a get_database_session()
function in main.py
, which will create and close the session in all of our routes:
def get_database_session(): try: db = SessionLocal() yield db finally: db.close()
Now, we’ll create HTML pages and render them to the client. First, we’ll create a template
folder in our project’s root directory to store our templates.
To render the pages, we’ll need to add some configurations to our server. In main.py
, we’ll import Jinja2Templates
from FastAPI to configure our template and StaticFiles
to configure our static files:
from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates
We’ll use the FastAPI mount
method to add the static files to our application, which requires the file path, directory, and a name for the static files:
app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates")
Then, create a static/css/style.css
file in your project’s root directory and add the following custom styles to it:
body{ background-color:rgb(236, 234, 232); } img { width: 100%; border-radius: 10px; } .image{ width: 30%; } .details{ width: 70%; margin: 12px } .card { border-radius: 20px; }
You can also save your JavaScript image folders and files in the static folder to render them to the client. Lastly, let’s create index.html
and overview.html
files in the templates directory. Add your HTML document to these files.
Now that we’ve created and configured our templates, let’s render them to the client. To begin, we must create a route for the index and overview pages. In main.py
, we’ll import Depends
and Request
from FastAPI, Session
from SQLAlchemy, and HTMLResponse
from FastAPI responses:
from fastapi import FastAPI, Depends, Request from sqlalchemy.orm import Session from fastapi.responses import HTMLResponse
You have to pass the request
as part of the key-value pairs in the context for Jinja2 in your request handler function, along with the database session, which will be dependent on the get_database_session()
function we created to manage our session across our routes. Then, we’ll query our database to retrieve our movies and render them with our index.html
template.
The overview route accepts a request parameter, which is used to query the database for the specific data using the filter
method. Then, it returns the first occurrence of the queried items. You can learn more about database queries in the FastAPI documentation.
When a request is made to these endpoints, we return a TemplateResponse
to the client with the movie object. Update your main.py
file with the code below:
@app.get("/movie", response_class=HTMLResponse) async def read_movies(request: Request, db: Session = Depends(get_database_session)): records = db.query(Movie).all() return templates.TemplateResponse("index.html", {"request": request, "data": records}) @app.get("/movie/{name}", response_class=HTMLResponse) def read_movie(request: Request, name: schema.Movie.name, db: Session = Depends(get_database_session)): item = db.query(Movie).filter(Movie.id==name).first() print(item) return templates.TemplateResponse("overview.html", {"request": request, "movie": item})
In our index.html
file, we’ll load our static file and display our data using Jinga2. We use url_for
to load our static file with a Jinja tag, passing the static file name and the path to the file:
<link href="{{ url_for('static', path='/style.css') }}" rel="stylesheet">
Then, we’ll loop through our movie objects and display them on our HTML page. Currently, we haven’t added any movies yet:
<div class="col-md-4 mb-2"> <div class="card"> <div class="card-body d-flex flex-row justify-content-between align-items-center"> <div class="image mb-2"> <img src="{{movie.url}}" alt=""> </div> <div class="details"> <a href="/movie/{{movie.id}}"> <h4>{{movie.name}}</h4></a> <div class="footer d-flex flex-row justify-content-between align-items-center"> <h6>{{movie.type}}</h6> <h6>{{movie.rating}}</h6> </div> </div> </div> </div> </div>
Now that we’ve successfully rendered our template on the client side, let’s create an HTML form that enables us to save movies to the database. First, we need to create an HTML form in index.html
:
<form action="/movie/" method="POST" enctype="application/x-www-form-urlencoded"> <div class="row"> <div class="col-md-6"> <label for="">Movie Name:</label> <input type="text" class="form-control" id="email" name="name" /> </div> <div class="col-md-6"> <label for="">Image URL:</label> <input type="text" class="form-control" name="url" /> </div> <div class="col-md-6"> <label for="">Type:</label> <select name="type" id="" class="form-control"> <option value=""></option> <option value="movie">movie</option> <option value="series">series</option> </select> </div> <div class="col-md-6"> <label for="">Rating:</label> <input type="number" class="form-control" name="rate" min="18" /> </div> <div class="col-md-12"> <label for="">Description:</label> <textarea name="desc" rows="5" class="form-control"></textarea> </div> <div class="col-md-6"> <button type="submit" class="btn btn-info mt-4">Save</button> </div> </div> </form>
When sending data to FastAPI, always encode your HTML form with the application/x-www-form-urlencoded
.
Before we can use the form in our application, we’ll need to import Form
from FastAPI and RedirectResponse
from Starlette into our main.py
file:
from fastapi import Depends, FastAPI, Request, Form from starlette.responses import RedirectResponse
Next, we’ll create a request handle, configure our form, and validate it with our database schema. Then, we’ll create an instance of our movie model, passing the data from the user to the model. Finally, we’ll add and save records to the database using the db.add
and db.commit
methods.
We’ll redirect the user back to the root route of our application using the FastAPI RedirectResponse
function, which accepts a URL and a status code as parameters:
@app.post("/movie/") async def create_movie(db: Session = Depends(get_database_session), name: schema.Movie.name = Form(...), url: schema.Movie.url = Form(...), rate: schema.Movie.rating = Form(...), type: schema.Movie.type = Form(...), desc: schema.Movie.desc = Form(...)): movie = Movie(name=name, url=url, rating=rate, type=type, desc=desc) db.add(movie) db.commit() response = RedirectResponse('/', status_code=303) return response
When redirecting a user from a POST route to a GET route, always include the 303 status code.
We need to create a route to enable users to update movies. HTML forms support only GET
and POST
requests, so we’ll configure our update route to accept JSON data. First, we need to import JSONResponse
from fastapi.responses
into main.py
:
from starlette.responses import `JSONResponse
Next, we’ll create a patch
route that will accept the movie ID as a parameter. Then, we get the user’s input from the request.json()
method and search the database for a movie with the specific ID.
We can update the movie’s name and description, refresh our database, convert the movie object to JSON, and return it to the client as a response:
@app.patch("/movie/{id}") async def update_movie(request: Request, id: int, db: Session = Depends(get_database_session)): requestBody = await request.json() movie = db.query(Movie).get(id) movie.name = requestBody['name'] movie.desc = requestBody['desc'] db.commit() db.refresh(movie) newMovie = jsonable_encoder(movie) return JSONResponse(status_code=200, content={ "status_code": 200, "message": "success", "movie": newMovie })
Now open the overview.html
file and add the update form:
<form method="POST" id="updateForm"> <div class="row"> <div class="col-md-12"> <label for="">Movie Name:</label> <input type="text" class="form-control" id="name" /> </div> <div class="col-md-12"> <label for="">Description:</label> <textarea id="desc" rows="5" class="form-control"></textarea> </div> <input type="hidden" id="id" value="{{movie.id}}" /> <div class="col-md-6"> <button type="submit" class="btn btn-info mt-4">Update</button> </div> </div> </form>
Next, we will use the JavaScript Fetch API to send a Patch
request to the server to update the movies. Add the code below to script.js
:
form = document.getElementById("updateForm"); function updateMovie(id, name, desc) { fetch("/movie/" + id, { method: "PATCH", body: JSON.stringify({ name, desc, }), }).then((response) => response.json()); window.location.reload(); } form.addEventListener("submit", (e) => { e.preventDefault(); const name = document.getElementById("name").value; const des = document.getElementById("desc").value; const id = document.getElementById("id").value; updateMovie(id, name, des); });
We also need the option to delete a movie from our database. To do so, we’ll need to create a delete route that accepts the movie ID as a parameter in our main.py
folder. We’ll search the database for the movie with that specific ID, then delete it using the db.delete()
method. When the movie is successfully deleted, we’ll send a null
value to the client:
@app.delete("/movie/{id}") async def delete_movie(request: Request, id: int, db: Session = Depends(get_database_session)): movie = db.query(Movie).get(id) db.delete(movie) db.commit() return JSONResponse(status_code=200, content={ "status_code": 200, "message": "success", "movie": None })
Next, in scripts.js
, we’ll send a Patch
request to the server to update the movies using the JavaScript Fetch API:
async function deleteMovie(id) { const res = await fetch("/movie/" + id, { method: "DELETE", }).then((response) => response.json()); console.log(res); }
In this tutorial, we explored FastAPI by building a simple movie database application. FastAPI is a great, modern framework for creating REST APIs. With FastAPI’s server-side rendering features, you can build and manage small-scale applications like websites and blogs without using any frontend framework.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
3 Replies to "Server-side rendering with FastAPI and MySQL"
Hi, thanks a tone for your super detailed tutorial!
Unfortunately, When I start the uvicorn server, I cannot connect to mysql because of an error as following
> sqlalchemy.exc.ProgrammingError: (mysql.connector.errors.ProgrammingError) 1045 (28000): Access denied for user ‘root’@’localhost’ (using password: NO)
Seems like I need to give my password in order to connect to mysql successfully.
Can you help fixing this? Thanks a lot!
It’s because you should use the password of your MySQL Sserver in the command : “root:password@localhost:3306/serversiderendering”
I guess the pydantic class attribute are typed annotated not assigned. id: int not id = int. I might be wrong tho, im not a fast api guy.