As noted by @MatsLindh in the comments section, root_path
(or --root-path
) does not change your application's prefix path, but is rather designated for behind the proxy cases, where "you might need to use a proxy server like Traefik or Nginx with a configuration that adds an extra path prefix that is not seen by your application" (see the relevant documentation).
As described in the documentation:
Proxy with a stripped path prefix
Having a proxy with a stripped path prefix, in this case, means that you could declare a path at
/app
in your code, but then, you add a layer on top (the proxy) that
would put your FastAPI application under a path like /api/v1
.
In this case, the original path /app
would actually be served at
/api/v1/app
.
Even though all your code is written assuming there's just /app
.
And the proxy would be "stripping" the path prefix on the fly before
transmitting the request to Uvicorn, keep your application convinced
that it is serving at /app
, so that you don't have to update all
your code to include the prefix /api/v1
.
Up to here, everything would work as normally.
But then, when you open the integrated docs UI (the frontend), it
would expect to get the OpenAPI schema at /openapi.json
, instead of
/api/v1/openapi.json
.
So, the frontend (that runs in the browser) would try to reach
/openapi.json
and wouldn't be able to get the OpenAPI schema (it would show "Failed to load API definition" error).
Because we have a proxy with a path prefix of /api/v1
for our app,
the frontend needs to fetch the OpenAPI schema at
/api/v1/openapi.json
. The docs UI would also need the OpenAPI
schema to declare that this API server is located at /api/v1
(behind
the proxy).
To achieve this, you can use the command line option --root-path
:
uvicorn main:app --root-path /api/v1
[...]
Alternatively, if you don't have a way to provide a command line
option like --root-path
or equivalent, you can set the root_path
parameter when creating your FastAPI app:
app = FastAPI(root_path="/api/v1")
Option 1
Hence, in your case, since you are not using a proxy, but rather need to have a custom prefix for your API, you could instead use an APIRouter
, which allows you to define a prefix
for the API routes (note that the prefix
must not include a final /
). You can either give the prefix
when instantiating the APIRouter
(e.g., router = APIRouter(prefix='/api/v1')
) or using .include_router()
, which, as described in the documentation, would allow you to include the same router multiple times with different prefix:
You can also use .include_router()
multiple times with the same
router using different prefixes.
This could be useful, for example, to expose the same API under
different prefixes, e.g. /api/v1
and /api/latest
.
This is an advanced usage that you might not really need, but it's
there in case you do.
The /app
endpoint in the example below can be accessed at http://127.0.0.1:8000/api/v1/app.
Working Example
from fastapi import FastAPI
from fastapi.routing import APIRouter
router = APIRouter()
@router.get('/app')
def main():
return 'Hello world!'
app = FastAPI()
app.include_router(router, prefix='/api/v1')
Once you have multiple versions of the API endpoints, you could use:
from fastapi import FastAPI
from fastapi.routing import APIRouter
router_v1 = APIRouter()
router_v2 = APIRouter()
@router_v1.get('/app')
def main():
return 'Hello world - v1'
@router_v2.get('/app')
def main():
return 'Hello world - v2'
app = FastAPI()
app.include_router(router_v1, prefix='/api/v1')
app.include_router(router_v2, prefix='/api/v2')
app.include_router(router_v2, prefix='/latest') # optional
Option 2
Alternatively, one could also mount sub-application(s) with the desired prefix
, as demonstrated in this answer and this answer (see Option 3).
As described in the documentation:
When you mount a sub-application as described above, FastAPI will take
care of communicating the mount path for the sub-application using a
mechanism from the ASGI specification called a root_path
.
That way, the sub-application will know to use that path prefix for
the docs UI.
And the sub-application could also have its own mounted
sub-applications and everything would work correctly, because FastAPI
handles all these root_path
s automatically.
Hence, in the example given below, you can access the /app
endpoint from the main app at http://127.0.0.1:8000/app and the /app
endpoint from the sub app at http://127.0.0.1:8000/api/v1/app. Similarly, the Swagger UI autodocs can be accessed at http://127.0.0.1:8000/docs and http://127.0.0.1:8000/api/v1/docs, respectively.
Working Example
from fastapi import FastAPI
app = FastAPI()
subapi = FastAPI()
@app.get('/app')
def read_main():
return {'message': 'Hello World from main app'}
@subapi.get('/app')
def read_sub():
return {'message': 'Hello World from sub API'}
app.mount('/api/v1', subapi)