Bigger Applications - Multiple Files
Warning
The current page still doesn’t have a translation for this language.
But you can help translating it: Contributing.
If you are building an application or a web API, it’s rarely the case that you can put everything on a single file.
FastAPI provides a convenience tool to structure your application while keeping all the flexibility.
Info
If you come from Flask, this would be the equivalent of Flask’s Blueprints.
An example file structure
Let’s say you have a file structure like this:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── routers
│ ├── __init__.py
│ ├── items.py
│ └── users.py
Tip
There are two __init__.py
files: one in each directory or subdirectory.
This is what allows importing code from one file into another.
For example, in app/main.py
you could have a line like:
from app.routers import items
- The
app
directory contains everything. - This
app
directory has an empty fileapp/__init__.py
.- So, the
app
directory is a “Python package” (a collection of “Python modules”).
- So, the
- The
app
directory also has aapp/main.py
file.- As it is inside a Python package directory (because there’s a file
__init__.py
), it is a “module” of that package:app.main
.
- As it is inside a Python package directory (because there’s a file
- There’s a subdirectory
app/routers/
. - The subdirectory
app/routers
also has an empty file__init__.py
.- So, it is a “Python subpackage”.
- The file
app/routers/items.py
is beside theapp/routers/__init__.py
.- So, it’s a submodule:
app.routers.items
.
- So, it’s a submodule:
- The file
app/routers/users.py
is beside theapp/routers/__init__.py
.- So, it’s a submodule:
app.routers.users
.
- So, it’s a submodule:
APIRouter
Let’s say the file dedicated to handling just users is the submodule at /app/routers/users.py
.
You want to have the path operations related to your users separated from the rest of the code, to keep it organized.
But it’s still part of the same FastAPI application/web API (it’s part of the same “Python Package”).
You can create the path operations for that module using APIRouter
.
Import APIRouter
You import it and create an “instance” the same way you would with the class FastAPI
:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Foo"}, {"username": "Bar"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Path operations with APIRouter
And then you use it to declare your path operations.
Use it the same way you would use the FastAPI
class:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Foo"}, {"username": "Bar"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
You can think of APIRouter
as a “mini FastAPI
“ class.
All the same options are supported.
All the same parameters, responses, dependencies, tags, etc.
Tip
In this example, the variable is called router
, but you can name it however you want.
We are going to include this APIrouter
in the main FastAPI
app, but first, let’s add another APIRouter
.
Another module with APIRouter
Let’s say you also have the endpoints dedicated to handling “Items” from your application in the module at app/routers/items.py
.
You have path operations for:
/items/
/items/{item_id}
It’s all the same structure as with app/routers/users.py
.
But let’s say that this time we are more lazy.
And we don’t want to have to explicitly type /items/
and tags=["items"]
in every path operation (we will be able to do it later):
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/")
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "foo":
raise HTTPException(status_code=403, detail="You can only update the item: foo")
return {"item_id": item_id, "name": "The Fighters"}
Add some custom tags
, responses
, and dependencies
We are not adding the prefix /items/
nor the tags=["items"]
to add them later.
But we can add custom tags
and responses
that will be applied to a specific path operation:
from fastapi import APIRouter, HTTPException
router = APIRouter()
@router.get("/")
async def read_items():
return [{"name": "Item Foo"}, {"name": "item Bar"}]
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"name": "Fake Specific Item", "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "foo":
raise HTTPException(status_code=403, detail="You can only update the item: foo")
return {"item_id": item_id, "name": "The Fighters"}
The main FastAPI
Now, let’s see the module at app/main.py
.
Here’s where you import and use the class FastAPI
.
This will be the main file in your application that ties everything together.
Import FastAPI
You import and create a FastAPI
class as normally:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Import the APIRouter
But this time we are not adding path operations directly with the FastAPI
app
.
We import the other submodules that have APIRouter
s:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
As the file app/routers/items.py
is part of the same Python package, we can import it using “dot notation”.
How the importing works
The section:
from .routers import items, users
Means:
- Starting in the same package that this module (the file
app/main.py
) lives in (the directoryapp/
)… - look for the subpackage
routers
(the directory atapp/routers/
)… - and from it, import the submodule
items
(the file atapp/routers/items.py
) andusers
(the file atapp/routers/users.py
)…
The module items
will have a variable router
(items.router
). This is the same one we created in the file app/routers/items.py
. It’s an APIRouter
. The same for the module users
.
We could also import them like:
from app.routers import items, users
Info
The first version is a “relative import”.
The second version is an “absolute import”.
To learn more about Python Packages and Modules, read the official Python documentation about Modules.
Avoid name collisions
We are importing the submodule items
directly, instead of importing just its variable router
.
This is because we also have another variable named router
in the submodule users
.
If we had imported one after the other, like:
from .routers.items import router
from .routers.users import router
The router
from users
would overwrite the one from items
and we wouldn’t be able to use them at the same time.
So, to be able to use both of them in the same file, we import the submodules directly:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Include an APIRouter
Now, let’s include the router
from the submodule users
:
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
Info
users.router
contains the APIRouter
inside of the file app/routers/users.py
.
With app.include_router()
we can add an APIRouter
to the main FastAPI
application.
It will include all the routes from that router as part of it.
Technical Details
It will actually internally create a path operation for each path operation that was declared in the APIRouter
.
So, behind the scenes, it will actually work as if everything was the same single app.
Check
You don’t have to worry about performance when including routers.
This will take microseconds and will only happen at startup.
So it won’t affect performance.
Include an APIRouter
with a prefix
, tags
, responses
, and dependencies
Now, let’s include the router from the items
submodule.
But, remember that we were lazy and didn’t add /items/
nor tags
to all the path operations?
We can add a prefix to all the path operations using the parameter prefix
of app.include_router()
.
As the path of each path operation has to start with /
, like in:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
…the prefix must not include a final /
.
So, the prefix in this case would be /items
.
We can also add a list of tags
that will be applied to all the path operations included in this router.
And we can add predefined responses
that will be included in all the path operations too.
And we can add a list of dependencies
that will be added to all the path operations in the router and will be executed/solved for each request made to them. Note that, much like dependencies in path operation decorators, no value will be passed to your path operation function.
from fastapi import Depends, FastAPI, Header, HTTPException
from .routers import items, users
app = FastAPI()
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
app.include_router(users.router)
app.include_router(
items.router,
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
The end result is that the item paths are now:
/items/
/items/{item_id}
…as we intended.
- They will be marked with a list of tags that contain a single string
"items"
. - The path operation that declared a
"custom"
tag will have both tags,items
andcustom
.- These “tags” are especially useful for the automatic interactive documentation systems (using OpenAPI).
- All of them will include the predefined
responses
. - The path operation that declared a custom
403
response will have both the predefined responses (404
) and the403
declared in it directly. - All these path operations will have the list of
dependencies
evaluated/executed before them.- If you also declare dependencies in a specific path operation, they will be executed too.
- The router dependencies are executed first, then the
dependencies
in the decorator, and then the normal parameter dependencies. - You can also add
Security
dependencies withscopes
.
Tip
Having dependencies
in a decorator can be used, for example, to require authentication for a whole group of path operations. Even if the dependencies are not added individually to each one of them.
Check
The prefix
, tags
, responses
and dependencies
parameters are (as in many other cases) just a feature from FastAPI to help you avoid code duplication.
Tip
You could also add path operations directly, for example with: @app.get(...)
.
Apart from app.include_router()
, in the same FastAPI app.
It would still work the same.
Very Technical Details
Note: this is a very technical detail that you probably can just skip.
The APIRouter
s are not “mounted”, they are not isolated from the rest of the application.
This is because we want to include their path operations in the OpenAPI schema and the user interfaces.
As we cannot just isolate them and “mount” them independently of the rest, the path operations are “cloned” (re-created), not included directly.
Check the automatic API docs
Now, run uvicorn
, using the module app.main
and the variable app
:
$ uvicorn app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
And open the docs at http://127.0.0.1:8000/docs.
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:
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.