1

Both the FastAPI backend and the Next.js frontend are running on localost. On the same computer, the frontend makes API calls using fetch without any issues. However, on a different computer on the same network, e.g., on 192.168.x.x, the frontend runs, but its API calls are no longer working.

I have tried using a proxy as next.js but that still does not work.

Frontend:


export default function People({setPerson}:PeopleProps)  {
 const fetcher = async (url:string) => await axios.get(url).then((res) => res.data);
 const { data, error, isLoading } = useSWR(`${process.env.NEXT_PUBLIC_API}/people`, fetcher);
  if (error) return <div>"Failed to load..."</div>;
  return (
      <>
        {isLoading? "Loading..." :data.map((person: Person) =>
        <div key={person.id}> {person.name} </div>)}
     </> 
  )
 }

The Next.js app loads the env.local file at startup, which contains: NEXT_PUBLIC_API=http://locahost:20002

Backend:

rom typing import List
from fastapi import APIRouter, Depends
from ..utils.db import get_session as db
from sqlmodel import Session, select
from ..schemas.person import Person, PersonRead
router = APIRouter()

@router.get("/people", response_model = List[PersonRead])
async def get_people(sess: Session = Depends(db)):
    res = sess.exec(select(Person)).all()
    return res 

The frontend runs with: npm run dev, and outputs

ready - started server on 0.0.0.0:3000, url: http://localhost:3000

The backend runs with: uvicorn hogar_api.main:app --port=20002 --host=0.0.0.0 --reload, and outputs:

INFO:     Uvicorn running on http://0.0.0.0:20002 (Press CTRL+C to quit)

When I open the browser on http://localhost:3000 on the same machine the list of Person is displayed on the screen.

When I open the browser on http://192.168.x.x:3000 on another machine on the same network, I get the "Failed to Load..." message.

When I open the FastAPI swagger docs on either machine, the documentation is displayed correctly and all the endpoints work as expected.

CORS look like this:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost:3000",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
  • Have you properly configured CORS on server side, as described [here](https://stackoverflow.com/a/71805329/17865804) and [here](https://stackoverflow.com/a/73963905/17865804)? – Chris Jan 07 '23 at 17:59
  • I updated the text to reflect the process.env content. And CORS should not be an issue since I configured them like the FastAPI docs suggest. I will consider the links you refer to as well. – Fede Camara Halac Jan 07 '23 at 18:04
  • Unfortunately, I can’t open the console on the other machine. I tried with my phone as well and it is also giving an error. True that I need to print the error instead of what I have here. – Fede Camara Halac Jan 07 '23 at 18:06
  • Exactly, that is the problem: the frontend only sees “localhost” even if I connect from 192.168.x.x… – Fede Camara Halac Jan 07 '23 at 18:13
  • I understand it is not very clear. I will try to clarify or ask again some other time – Fede Camara Halac Jan 07 '23 at 18:18
  • Will do! Thanks a lot. I will report back asap when I get to the code – Fede Camara Halac Jan 07 '23 at 18:34
  • Still no luck. I configured CORS with the new origin: `http://192.168.178.23:3000` and enabled an `error.message` display on the screen. On my phone browser, I go to `http://192.168.178.23:3000` and see "Failed to load... Network Error" – Fede Camara Halac Jan 08 '23 at 11:13
  • OK. Finally, the trick was adding the new domain to the CORS list of origins AND using that same domain as the address for fetching, ie, loading it in the `env.local` file for the frontend to use – Fede Camara Halac Jan 08 '23 at 11:24

2 Answers2

9

Setting the host flag to 0.0.0.0

To access a FastAPI backend from a different machine/IP (than the local machine that is running the server) on the same network, you would need to make sure that the host flag is set to 0.0.0.0. The IP address 0.0.0.0 means all IPv4 addresses on the local machine. If a host has two IP addresses, e.g., 192.168.10.2 and 10.1.2.5, and the server running on the host listens on 0.0.0.0, it will be reachable at both of those IPs. For example, through command line interface:

uvicorn main:app --host 0.0.0.0 --port 8000

or, programmatically:

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

Note that RFC 1122 prohibits 0.0.0.0 as a destination address in IPv4 and only allows it as a source address, meaning that you can't type, for instance, http://0.0.0.0:8000 in the address bar of the browser and expect it to work. You should instead use one of the IPv4 addresses of the local machine, e.g., http://192.168.10.2:8000 (or, if you are testing the API on the same local machine running the server, you could use http://127.0.0.1:8000 or http://localhost:8000).

Adjusting Firewall Settings

You may also need to adjust your Firewall to allow external access to the port you specified (by creating an inbound firewall rule for Python—on Windows, this is usually automatically created when allowing a program, Python in this case, to communicate through Windows Firewall, and by default this allows traffic on Any port for both TCP and UDP connections).

Adjusting CORS Settings

Additionally, if your frontend is listening on a separate IP address and/or port number from the backend, please make sure to have CORS enabled and properly configured, as desribed in this answer and this answer. For example:

origins = ['http://localhost:3000','http://192.168.178.23:3000']

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

Making HTTP requests in JavaScript

Finally, please take a look at this answer and this answer regarding using the proper origin/URL when issuing a JavaScript fetch request from the frontend. In short, in your JavaScript asynchronous request, you should use the same domain name you typed in the address bar of your browser (but with the port number your backend server is listening on). If, for example, both the backend and the frontend server are listening on the same IP address and port number, e.g., 192.168.178.23:8000—that would be the case when using Jinja2Templates for instance—you could access the frontend by typing in the address bar of the browser the url leading to the frontend page, e.g., http://192.168.178.23:8000/, and the fetch request should look like this:

fetch('http://192.168.178.23:8000/people', {...

For convenience, in the above case—i.e., when both the backend and the frontend are running on the same machine and listening on the same port—you could use relative paths, as suggested in a linked answer above. Note that if you are testing your application locally on the same machine and not from a different machine on LAN, and instead using 127.0.0.1 or localhost to access the frontend/backend, those two are different domains/origins. Thus, if you typed http://127.0.0.1:8000/ in the address bar of the browser to access the frontend page, you shouldn't make fetch requests using, for instance, fetch('http://localhost:8000/people', as you would get a CORS error (e.g., Access to fetch at [...] from origin [...] has been blocked by CORS policy...). You should instead use fetch('http://127.0.0.1:8000/people', and vice versa.

Otherwise, if the frontend's origin differs from the backend's (see this answer for more details on origin), it should then be added to the list of origins in the CORS settings of the backend (see example above).

Chris
  • 18,724
  • 6
  • 46
  • 80
  • Thanks for your answer. I am already running `uvicorn` specifying `host` as you suggest. I *can* connect to FastAPI using 168.192.x.x:PORT.... What I cannot do is have the *frontend* connect to the *api* using `fetch`. – Fede Camara Halac Jan 07 '23 at 16:11
  • Will edit my post with your instructions. Thanks for your time. – Fede Camara Halac Jan 07 '23 at 17:03
  • @Chris I'm developing on a Windows 11 laptop. The fastapi/uvicorn server runs inside terminal#1 on 0.0.0.0:8080. The client is a python program using 'requests' inside terminal#2. The client makes a request to localhost:8080. CORS has been set on the server with options=["*"] (also tried with options=["http://localhost:8080"]. But, the query response for a get or post is always 'HTTP/1.1 404 Not Found' :(. The get response to httpbin.org is 'HTTP/1.1 200 OK'. Any ideas? Thanks – Henry Thornton Feb 20 '23 at 09:35
  • @HenryThornton You can find client-server working examples [here](https://stackoverflow.com/a/70657621/17865804), as well as [here](https://stackoverflow.com/a/70640522/17865804) and [here](https://stackoverflow.com/a/71741617/17865804) – Chris Feb 20 '23 at 11:47
  • @Chris FastApi is used as an http server only. The request is from a Python client for testing but in production, it could be a web client, a native app etc. The listed examples didn't help :( – Henry Thornton Feb 20 '23 at 15:04
0

This problem stonewalled me for days - I'm using fastapi/uvicorn on Mac, in Python 3.9.

When setting the uvicorn host to 0.0.0.0, after startup I checked and found that it only binds to TCP 127.0.0.1:

sudo lsof -PiTCP -sTCP:LISTEN

So I dug into the uvicorn code, the solution was in this file: /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/uvicorn/config.py

Simply change this: sock.bind((self.host, self.port)) to this: sock.bind(('0.0.0.0', self.port))

After this change I restarted uvicorn and I can access the page from any machine on my network.

Hope this helps someone!