6

I have a FastAPI application running on my local machine under the URL: http://localhost:8000, using the following Python code:

from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "*"
    '''
    "http://localhost:8000/add_points/",
    "http://localhost:8000/check_points/",
    "http://localhost:8000/check_item_points/",
    "http://localhost:8000/redeem_points/"
    '''
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

users = {"matt": 0}
items = {"ticket": 7}


class User(BaseModel):
    name: str
    points: float
    item: str


class AddTransaction(BaseModel):
    name: str
    points: float


class UserPoints(BaseModel):  # anything that extnds this base model is a pyantic model
    name: str
    points: float


class Item(BaseModel):
    name: str
    points: float


# -----Post Requests-----
@app.post("/add_points/")
def add_points(add_transaction: AddTransaction):
    global users
    user_id = add_transaction.name
    points = add_transaction.points
    users[user_id] = users.get(user_id, 0) + points
    return users[user_id]


@app.post("/check_points/")
def check_points(user_points: UserPoints):
    global users
    user_id = user_points.name
    points = user_points.points
    return users[user_id], points


@app.post("/check_item_points/")
def check_item_points(item: Item):
    global items
    item_id = item.name
    points = item.points
    return item[item_id], points


@app.post("/redeem_points/")  # user spends points (they lose points) gain an item
def redeem_points(add_transaction: AddTransaction, user_points: UserPoints, item: Item, user: User):
    global users
    global items
    user_id = add_transaction.name
    user_points = user_points.points
    item_points = item.points
    item_pre = item.name
    item_post = user.item
    if user_points >= item_points:
        user_points == user_points - item_points
        item_post == item_pre
        return users[user_id], users[user_points], users[item_post]
    else:
        return "insufficient funds"


# -----Get Requests-----
@app.get("/")
def read_root():
    return {"Hello": "World"}


# -----Put Requests-----
""""
@app.put("/items/{item_id}")
def update_item(item_id: int, item:Item):
    return {"item_name": item.name, "item_id": item_id}
"""

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

I also have an HTML file with JS script inside that simply sends a POST request to http://localhost:8000/add_points/ upon the click of a button. Here is the code for that:

<!DOCTYPE html>
<html>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<body>
<br><br><br><span style="text-align: center;"><button id="send">Send Request</button></span>
</body>

<script>
$("#send").on("click", evt => {
    $.post("http://localhost:8000/add_points/",
  {
    name: "string",
    points: 5.0
  },
  function(data, status){
    alert("Data: " + data + "\nStatus: " + status);
  });
});
</script>

</html>

However, when I try to send the POST request I get the following errors in PyCharm:

INFO: 127.0.0.1:49413 - "OPTIONS /add_points/ HTTP/1.1" 400 Bad Request

INFO: 127.0.0.1:49413 - "POST /add_points/ HTTP/1.1" 422 Unprocessable Entity

I understand that at least one of these errors originates from the CORS policy restrictions, however, this project is aimed towards mobile phone users who should not have to install any browser extensions for overriding the policy. Any advice as to how to fix these errors would be greatly appreciated!

EDIT UPDATE:

const url = new URL('localhost:8000/add_points/');
$("#send").on("click", evt => { fetch(url, 
      { 
        method: 'POST', 
       headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}, 
          body: JSON.stringify({"name":"John", "points":50.0}) 
      }).then().catch((error) => { console.log(error.message); }); }); 

I'm still getting a 400 Bad Request error.

1

1 Answer 1

1

When accessing the frontend from a Jinja2 template or an HTMLResponse—which seems to be the case, as shown from the port that your app is listening on, as well as the origins you defined in the CORSMiddleware—you don't need to have CORS enabled for the application. As described in this answer and this answer, two objects have the same origin only when the protocol, domain and port all match.

Please note that localhost and 127.0.0.1 are considered to be different origins. Hence, if you are accessing your fronend from a web browser (by typing the URL in the address bar) at http://127.0.0.1:8000/, then your asynchronous JS request (using fetch, for instance) should use that domain; for example:

fetch('http://127.0.0.1:8000/add',...

not

fetch('http://localhost:8000/add',...

and vice versa. You can always use relative paths for your fetch requests (as shown below), and hence, your app will work regardless of the domain name you used (either localhost or 127.0.0.1) for accessing the frontend from your browser.

Working Example:

See related answers here, here and here as well.

app.py

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

app = FastAPI()
templates = Jinja2Templates(directory="templates")

class Item(BaseModel):
    name: str
    points: float

@app.post("/add")
def add_points(item: Item):
    return item
    
@app.get("/")
def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

templates/index.html

<!DOCTYPE html>
<html>
   <body>
      <input type="button" value="Submit" onclick="submit()">
      <div id="responseArea"></div>
      <script>
         function submit() {
            fetch('/add', {
                 method: 'POST',
                 headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                 },
                 body: JSON.stringify({'name': 'John', 'points': 50.0})
              })
              .then(resp => resp.text()) // or, resp.json(), etc.
              .then(data => {
                 document.getElementById("responseArea").innerHTML = data;
              })
              .catch(error => {
                 console.error(error);
              });
         }
      </script>
   </body>
</html>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.