DEV Community

Cover image for Building a Simple Blog App Using FastAPI, HTML, CSS, and JSON
Jagroop Singh
Jagroop Singh

Posted on • Updated on

Building a Simple Blog App Using FastAPI, HTML, CSS, and JSON

In this tutorial, we will create a basic blog app using FastAPI for the backend, HTML and CSS for the frontend, and a JSON file for performing basic CRUD (Create, Read, Update, Delete) operations.
FastAPI is a modern web framework for building APIs with Python, which is known for its simplicity, speed, and built-in support for asynchronous operations.

Below Implementation would looks like this :

Blog List Page

Blog Detail Page

Create New Blog Page

Prerequisites

Before getting started, ensure you have the following installed:

  • Python 3.7+
  • FastAPI
  • Uvicorn (for running FastAPI applications)

To install FastAPI and Uvicorn, you can use pip:

pip install fastapi uvicorn python-multipart Jinja2
Enter fullscreen mode Exit fullscreen mode

Project Structure

Here's how the project will be structured:

/blog_app
    ├── static
    │   └── style.css
    ├── templates
    │   ├── index.html
    │   ├── post.html
    │   ├── create_post.html
    ├── blog.json
    ├── main.py

Enter fullscreen mode Exit fullscreen mode

Step 1: Setting Up FastAPI

Create a main.py file which will contain the FastAPI application.

from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import json
import os

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="templates")

# Load or initialize blog data
BLOG_FILE = "blog.json"

if not os.path.exists(BLOG_FILE):
    with open(BLOG_FILE, "w") as f:
        json.dump([], f)


def read_blog_data():
    with open(BLOG_FILE, "r") as f:
        return json.load(f)


def write_blog_data(data):
    with open(BLOG_FILE, "w") as f:
        json.dump(data, f)


@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    blogs = read_blog_data()
    return templates.TemplateResponse("index.html", {"request": request, "blogs": blogs})


@app.get("/post/{post_id}", response_class=HTMLResponse)
async def read_post(request: Request, post_id: int):
    blogs = read_blog_data()
    post = blogs[post_id] if 0 <= post_id < len(blogs) else None
    return templates.TemplateResponse("post.html", {"request": request, "post": post})


@app.get("/create_post", response_class=HTMLResponse)
async def create_post_form(request: Request):
    return templates.TemplateResponse("create_post.html", {"request": request})


@app.post("/create_post")
async def create_post(title: str = Form(...), content: str = Form(...)):
    blogs = read_blog_data()
    blogs.append({"title": title, "content": content})
    write_blog_data(blogs)
    return RedirectResponse("/", status_code=303)


@app.post("/delete_post/{post_id}")
async def delete_post(post_id: int):
    blogs = read_blog_data()
    if 0 <= post_id < len(blogs):
        blogs.pop(post_id)
        write_blog_data(blogs)
    return RedirectResponse("/", status_code=303)

Enter fullscreen mode Exit fullscreen mode

Step 2: Setting Up HTML and CSS

In the templates folder, create the following HTML files:

index.html
This file will list all the blog posts.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog App</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>Blog Posts</h1>
    <a href="/create_post">Create New Post</a>
    <div>
        {% for post in blogs %}
        <div class="post">
            <h2>{{ post.title }}</h2>
            <p>{{ post.content[:100] }}...</p>
            <a href="/post/{{ loop.index0 }}">Read more</a>
            <form method="post" action="/delete_post/{{ loop.index0 }}">
                <button type="submit">Delete</button>
            </form>
        </div>
        {% endfor %}
    </div>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

post.html
This file will display the full content of a blog post.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ post.title }}</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <a href="/">Back to Home</a>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

create_post.html
This file will contain the form for creating a new post.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Create a New Post</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>Create a New Post</h1>
    <form method="post" action="/create_post">
        <label for="title">Title</label>
        <input type="text" name="title" id="title" required>
        <label for="content">Content</label>
        <textarea name="content" id="content" required></textarea>
        <button type="submit">Create</button>
    </form>
    <a href="/">Back to Home</a>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

Step 3: Styling with CSS

In the static folder, create a style.css file to add some basic styling.

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    background-color: #f0f0f0;
}

h1 {
    color: #333;
}

a {
    text-decoration: none;
    color: #0066cc;
}

.post {
    background-color: #fff;
    padding: 10px;
    margin-bottom: 15px;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

button {
    background-color: #ff4d4d;
    border: none;
    padding: 5px 10px;
    color: white;
    border-radius: 3px;
    cursor: pointer;
}

button:hover {
    background-color: #ff1a1a;
}

input, textarea {
    width: 100%;
    padding: 8px;
    margin-bottom: 10px;
}

Enter fullscreen mode Exit fullscreen mode

Step 4: Running the Application

Now that everything is set up, run the FastAPI application using Uvicorn.

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

Visit http://127.0.0.1:8000 in your browser, and you should see the blog homepage.

As an assignment, you can use a Database 🗄️ instead of just JSON to create a full-stack web app.
With a database, you can add more features 🌟, improve performance 🚀, and enhance the overall UI/UX 🎨 for a richer user experience.

That's all for this blog! Stay tuned for more updates and keep building amazing apps! 💻✨

Top comments (25)

Collapse
 
cshalive profile image
Chandra

Request to add - how to make this application public. This article shows hosting on a local host. If you could add how to host it public, it would be a complete - end to end - coverage. And would be very helpful.
Thank you.

Collapse
 
jagroop2001 profile image
Jagroop Singh

@cshalive ,I'm thinking about it as well. Let me put this on my "to do" list for blog posts.

Collapse
 
cshalive profile image
Chandra

Thank you.
By the way, I ran in to error while using the above code.
The error was
AssertionError: jinja2 must be installed to use Jinja2Templates

Not sure why the code at line #4 did not work as expected
from fastapi.templating import Jinja2Templates

I had to fix it by installing Jinja2 separately

Thread Thread
 
jagroop2001 profile image
Jagroop Singh

install this pip install Jinja2. Let me know if it works.

Thread Thread
 
cshalive profile image
Chandra

Yes, that's how I fixed.

Look forward to your blog post on how to host this public
and also integrated with database. Two good topics ;)

Thread Thread
 
jagroop2001 profile image
Jagroop Singh

Yes, the hosting fastapi project is expected to arrive  next week, and the database integration will published this week. @cshalive

Collapse
 
andrewbaisden profile image
Andrew Baisden

Great tutorial!

Collapse
 
jagroop2001 profile image
Jagroop Singh
Collapse
 
jayantbh profile image
Jayant Bhawal

🤔
There's basically contents of just 4 files in here with no further reasoning or underlying explanation.

I think pointing people to a repo instead, and explaining how things work might be a better idea. :)

Collapse
 
jagroop2001 profile image
Jagroop Singh

sure !!, Next time will do that.

Collapse
 
alexanderwalsh profile image
Alexander Walsh

Nice demonstration of FastApi crud with html. Please, can you do the same thing, but this time, getting data from sql lite or MySQL database? Thanks

Collapse
 
jagroop2001 profile image
Jagroop Singh

Sure @alexanderwalsh !!
Next blog will be coming with db.

Collapse
 
swoopsavvy profile image
SwoopSavvy

It will be great if thats PostgreSQL !

Thread Thread
 
jagroop2001 profile image
Jagroop Singh • Edited

You want PostgreSQL, okay next week new blog with PostgreSQL will be coming. @swoopsavvy

Collapse
 
indra_kiran_b50d9ee74a55b profile image
indra kiran

Hi @jagroop2001

I successfully ran the program in the browser. It would be very beneficial if you could provide a detailed explanation of how it works, including a breakdown of each concept and its functionality within the code.

Collapse
 
jagroop2001 profile image
Jagroop Singh

Yes, I will address this in my upcoming blog post on implementing FastAPI with a database (Postgres or Mongo). @indra_kiran_b50d9ee74a55b

Collapse
 
works profile image
Web

Excellent introduction to FastAPI tutorial; I hope to see a blog post about database integration soon. @jagroop2001

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @works , sure I will come up with blog regarding db integration

Collapse
 
jottyjohn profile image
Jotty John

Great!

Collapse
 
jagroop2001 profile image
Jagroop Singh

Thanks @jottyjohn

Collapse
 
john12 profile image
john • Edited

Why python-multipart is installed in this ? @jagroop2001

Collapse
 
jagroop2001 profile image
Jagroop Singh

@john12 ,python-multipart is typically installed in FastAPI projects that involve handling multipart form data ( like sending formdata from frontend to backend)

Collapse
 
john12 profile image
john

okay, got it !!

Collapse
 
syedmuhammadaliraza profile image
Syed Muhammad Ali Raza

ni ce

Collapse
 
jagroop2001 profile image
Jagroop Singh