How does FastAPI's application mounting works?

11k views Asked by At

For certain reasons, we have chosen the FastAPI, in order to use it as back-end tier of our multi-module production. One of its attractive features is sub application, that helps us to separate different modules with intention of making it more modular. But we are concerned about some possible deficiencies which are missing in the official documentation. There are a considerable amount of common things -- e.g data, services, etc -- that we need to share them between main module and submodule through plugins, middle-wares and dependency-injection. The questions are: Is this feature good enough for separate modules? and so: Do sub applications inherit middle-ware, plugins and dependency injection from parent app?

thanks for sharing your experiences.

the sample code in the official docs

from fastapi import FastAPI

app = FastAPI()


@app.get("/app")
def read_main():
    return {"message": "Hello World from main app"}


subapi = FastAPI()


@subapi.get("/sub")
def read_sub():
    return {"message": "Hello World from sub API"}


app.mount("/subapi", subapi)
1

There are 1 answers

3
Yagiz Degirmenci On BEST ANSWER

I think documentation is pretty clear about it.

"Mounting" means adding a completely "independent" application.

Whatsoever, let's keep going from your example.

This is what we got for our subapi's routes.

[{"path":route.path} for route in subapi.routes] = [
     {'path': '/openapi.json'},
     {'path': '/docs'},
     {'path': '/docs/oauth2-redirect'},
     {'path': '/redoc'},
     {'path': '/sub'}
     ]

This is what we got for app's routes.

[{"path":route.path} for route in app.routes] = [{'path': '/openapi.json'},
     {'path': '/docs'},
     {'path': '/docs/oauth2-redirect'},
     {'path': '/redoc'},
     {'path': '/app'},
     {'path': '/subapi'}
     ]

That's quite interesting because our subapi did not inherited /app, let's keep going and make things more interesting, let us run our app with a single command

uvicorn my_app_name:app 
  • As expected we have our app's documentation in /docs

  • Also we have subapi's documentation in /subapi/docs, there is not interesting here.

So what we should expect, when we add this?

subapi.mount("/app", app)

Let's run it again, but this time let's call subapi.

uvicorn my_app_name:subapi

What do we expect to see?

  • By default we should have subapi's documentation in /docs
  • The app's documentation in the /app/docs

Yes, we are right, but things goes interesting from here.

Now we have an application like Matryoshka dolls

When we send a request to /app/subapi/sub (Remind we ran our app with uvicorn my_app_name:subapi)

curl http://127.0.0.1:8000/app/subapi/sub

Out: {"message":"Hello World from sub API"}

Seems like it's working fine but let's try more.

What about /app/subapi/app/subapi/app/subapi/app/subapi/app/subapi/app/app

curl http://127.0.0.1:8000/app/subapi/app/subapi/app/subapi/app/subapi/app/subapi/app/app

Out: {"message":"Hello World from main app"}

Are you confused? Don't be, let me explain.

When you mount a sub-application, FastAPI takes care of the mounted app, using a mechanism from the ASGI specification called a root_path

What root_path does and why the example above worked?

Straight-forward root_path says, you can reach all the routes that you defined in your app.routes from your root_path, let's visualize this.

Now our root_path is /app

/app/

Let's add subapi, and it became our root_path.

/app/subapi/

Let's add app again, and it became our root_path

/app/subapi/app
  • Note: The example above worked because, we mounted two apps together.

Are you not satisfied and you are saying what if I add a middleware, what is going to happen?

Easy to answer, it will not inherit.

Let me explain this with a simple example, I am going to add a middleware for my subapi.

from fastapi.middleware.cors import CORSMiddleware

subapi.add_middleware(CORSMiddleware)

All the data for your application is inside of __dict__

So we can find out the difference easily by checking the 'user_middleware' key.

subapi.__dict__['user_middleware'] = [Middleware(CORSMiddleware)]
app.__dict__['user_middleware'] = []

All the other things you add etc will work independently because they are totally different applications underneath, so you will use mounting safely.

Conclusion

  • Yes, they will work independently.