Question
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=["*"],
)
Answer
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 created automatically, 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).