Home FastAPI + Front-End + Database Integration(English)
Post
Cancel

FastAPI + Front-End + Database Integration(English)

FastAPI + Front-End + Database Integration

We are going to complete the following integration

Desktop View

Due to the length of the front-end and back-end code, I’ll first discuss the issues encountered to prevent any confusion after seeing the complete code.

Error Message

fastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that class 'item_to_db.Item' is a valid Pydantic field type. If you are using a return type annotation that is not a valid Pydantic field (e.g. Union[Response, dict, None]) you can disable generating the response model from the type annotation with the path operation decorator parameter response_model=None.

Many people might encounter this error: This error occurs because the FastAPI route function returns an SQLAlchemy ORM model, but FastAPI expects a Pydantic model. To solve this, we need to use Pydantic models to define our data structures for data validation and response returns.

In simple terms, you’re trying to forcefully use a data structure meant for database interaction for the front-end.

Solution:

Create ORM models and Pydantic models and enable conversion between the two.

ORM Model

1
2
3
4
5
6
7
# define Database model
class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String, index=True)

Pydantic Model

1
2
3
4
5
6
7
8
9
10
11
12
13
# define Pydantic Model
class ItemCreate(BaseModel):
    id:int
    name: str
    description: str

class ItemRead(BaseModel):
    id: int
    name: str
    description: str

    class Config:
        orm_mode = True

Let’s take a closer look at the create_item function.

1
2
3
4
5
6
7
8
# create_item
@app.post("/items/", response_model=ItemRead)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
    db_item = Item(id=item.id, name=item.name, description=item.description)
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

The response_model=ItemRead specifies that the response model for this route is ItemRead. This means that after the function executes, the returned data will be converted into an ItemRead object and returned to the user, which is a Pydantic model.

db_item = Item(id=item.id, name=item.name, description=item.description) Here, we create a new Item object db_item based on the user-submitted data item. We use the id, name, and description properties from the ItemCreate model to initialize this object. This means we have to write the logic to convert the Pydantic model to the ORM model to interact with the database.

Fastapi codes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# frontend <-> backend <-> database

from typing import List
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pydantic import BaseModel


# Set up the database connection
DATABASE_URL = 'postgresql://myuser:mypassword@172.17.0.2:5432/mydatabase'
engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()


class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String, index=True)

# Create the database tables
Base.metadata.create_all(bind=engine)

# Define the Pydantic models
class ItemCreate(BaseModel):
    id:int
    name: str
    description: str

class ItemRead(BaseModel):
    id: int
    name: str
    description: str

    class Config:
        orm_mode = True

# Create the FastAPI application
app = FastAPI()


# Configure CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost"],  # allowed source
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Dependency - get the database session
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Create a new item
@app.post("/items/", response_model=ItemRead)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
    db_item = Item(id=item.id, name=item.name, description=item.description)
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

# Read items
@app.get("/items/{item_id}", response_model=ItemRead)
def read_item(item_id: int, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item not found")
    return item

# Update items
@app.put("/items/{item_id}", response_model=ItemRead)
def update_item(item_id: int, updated_item: ItemCreate, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item not found")

    item.name = updated_item.name
    item.description = updated_item.description
    db.commit()
    db.refresh(item)
    return item

# Delete items
@app.delete("/items/{item_id}", response_model=dict)
def delete_item(item_id: int, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if item is None:
        raise HTTPException(status_code=404, detail="Item not found")

    db.delete(item)
    db.commit()
    return {"detail": "Item deleted"}

# Delete all items
@app.delete("/items/", response_model=dict)
def delete_all_items(db: Session = Depends(get_db)):
    db.query(Item).delete()
    db.commit()
    return {"detail": "All items deleted"}

# Get all items
@app.get("/items/", response_model=List[ItemRead])
def get_all_items(db: Session = Depends(get_db)):
    items = db.query(Item).all()
    return items

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<!DOCTYPE html>
<html>
<head>
    <title>FastAPI CRUD Operations</title>
    <script>

        const baseURL = 'http://localhost:8000';

        async function createItem() {
            const id = document.getElementById('create-id').value;
            const name = document.getElementById('create-name').value;
            const description = document.getElementById('create-description').value;

            const response = await fetch(`${baseURL}/items/`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id: parseInt(id), name: name, description: description })
            });
            const result = await response.json();
            alert(JSON.stringify(result));
        }

        async function readItem() {
            const id = document.getElementById('read-id').value;
            const response = await fetch(`${baseURL}/items/${id}`, { method: 'GET' });
            const result = await response.json();
            alert(JSON.stringify(result));
        }

        async function updateItem() {
            const id = document.getElementById('update-id').value;
            const name = document.getElementById('update-name').value;
            const description = document.getElementById('update-description').value;

            const response = await fetch(`${baseURL}/items/${id}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id: parseInt(id), name: name, description: description })
            });
            const result = await response.json();
            alert(JSON.stringify(result));
        }

        async function deleteItem() {
            const id = document.getElementById('delete-id').value;
            const response = await fetch(`${baseURL}/items/${id}`, { method: 'DELETE' });
            const result = await response.json();
            alert(JSON.stringify(result));
        }

        async function deleteAllItem() {
            try {
                const response = await fetch(`${baseURL}/items/`, {
                    method: 'DELETE',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });

                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }

                const result = await response.json();
                alert(JSON.stringify(result));
            } catch (error) {
                console.error('Error:', error);
                alert('An error occurred while deleting all items.');
            }
        }

        async function getAllItems() {
            try {
                const response = await fetch(`${baseURL}/items/`);

                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }

                const items = await response.json();
                const itemsList = document.getElementById('itemsList');

                items.forEach(item => {
                    const li = document.createElement('li');
                    li.textContent = `${item.id}: ${item.name} - ${item.description}`;
                    itemsList.appendChild(li);
                });
            } catch (error) {
                console.error('Error:', error);
                alert('An error occurred while getting items.');
            }
        }

        window.addEventListener('load', getAllItems);

    </script>
</head>
<body>
    <h1>FastAPI CRUD Operations</h1>

    <h1>All Items</h1>
    <ul id="itemsList"></ul>

    <h2>Create Item</h2>
    <label>ID:</label><input type="text" id="create-id"><br>
    <label>Name:</label><input type="text" id="create-name"><br>
    <label>Description:</label><input type="text" id="create-description"><br>
    <button onclick="createItem()">Create</button><br>

    <h2>Read Item</h2>
    <label>ID:</label><input type="text" id="read-id"><br>
    <button onclick="readItem()">Read</button><br>

    <h2>Update Item</h2>
    <label>ID:</label><input type="text" id="update-id"><br>
    <label>Name:</label><input type="text" id="update-name"><br>
    <label>Description:</label><input type="text" id="update-description"><br>
    <button onclick="updateItem()">Update</button><br>

    <h2>Delete Item</h2>
    <label>ID:</label><input type="text" id="delete-id"><br>
    <button onclick="deleteItem()">Delete</button><br>

    <h2>Delete All Items</h2>
    <button onclick="deleteAllItem()">Delete</button><br>



</body>
</html>

Although the code is lengthy, the main issue is that the front-end only works with Pydantic models, while the database only works with SQLAlchemy ORM models, so you need to remember to do the conversion.

Demonstration

Due to limited space, the demonstration will be brief.

Web page just looks like following

Desktop View

Add an item

Desktop View

Update the item information

Desktop View

Desktop View

OK, Done !!!

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍