{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/2026-08-06-fastapi-dependency-injection-explained/","result":{"data":{"site":{"siteMetadata":{"title":"M.Hassan Ahmed","author":"Hassan11196"}},"markdownRemark":{"id":"9799f54d-383c-5c54-91d3-7fd06672e250","excerpt":"Every endpoint in a real backend needs the same handful of things before it can do any work: a database session, the authenticated user, a settings object…","html":"<p>Every endpoint in a real backend needs the same handful of things before it can do any work: a database session, the authenticated user, a settings object, maybe a rate limiter. You can build those inline at the top of each handler. It works, and it rots. The construction code gets copy-pasted, the teardown gets forgotten, and the day you want to run the handler in a test, you find there is no seam to swap the real database for a fake one.</p>\n<p>FastAPI’s answer is <a href=\"https://fastapi.tiangolo.com/tutorial/dependencies/\"><code class=\"language-text\">Depends</code></a>: you declare what a handler needs as a parameter, and the framework builds it before the handler runs. This post is for engineers who already use <code class=\"language-text\">Depends</code> and want to know what it does underneath. It covers how a dependency graph gets resolved, why a shared dependency runs only once, what <code class=\"language-text\">yield</code> actually guarantees about cleanup, and the places it leaks.</p>\n<p>The examples follow the shape of the <a href=\"/project/archi/\">Archi</a> backend I worked on for CMS computing operations at CERN. There, every request has to be authenticated against CERN SSO (single sign-on) and handed a scoped database session before it touches a thing.</p>\n<h2><code class=\"language-text\">Depends</code> is a resolver, not a container</h2>\n<p>If your mental model of dependency injection comes from Spring or .NET, drop the part about a global registry. There is no container you register services into, and no separate file that configures lifetimes. A dependency in FastAPI is just a callable. <code class=\"language-text\">Depends(get_db)</code> on a parameter means one thing: before you call this function, call <code class=\"language-text\">get_db</code>, and pass what it returns as this argument.</p>\n<p>In this example, the endpoint needs a user, and getting the user needs a token and settings:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">from</span> fastapi <span class=\"token keyword\">import</span> Depends<span class=\"token punctuation\">,</span> FastAPI\n\napp <span class=\"token operator\">=</span> FastAPI<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">get_settings</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> Settings<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> Settings<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>  <span class=\"token comment\"># env-driven config</span>\n\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">get_current_user</span><span class=\"token punctuation\">(</span>\n    token<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span> <span class=\"token operator\">=</span> Depends<span class=\"token punctuation\">(</span>get_token<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    settings<span class=\"token punctuation\">:</span> Settings <span class=\"token operator\">=</span> Depends<span class=\"token punctuation\">(</span>get_settings<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> User<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> verify<span class=\"token punctuation\">(</span>token<span class=\"token punctuation\">,</span> settings<span class=\"token punctuation\">.</span>jwt_key<span class=\"token punctuation\">)</span>\n\n<span class=\"token decorator annotation punctuation\">@app<span class=\"token punctuation\">.</span>post</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/documents\"</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">create_document</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">:</span> User <span class=\"token operator\">=</span> Depends<span class=\"token punctuation\">(</span>get_current_user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"owner\"</span><span class=\"token punctuation\">:</span> user<span class=\"token punctuation\">.</span><span class=\"token builtin\">id</span><span class=\"token punctuation\">}</span></code></pre></div>\n<p>The wiring is the function signature. <code class=\"language-text\">get_current_user</code> needs a token and settings, so it declares them as <code class=\"language-text\">Depends</code> parameters, and FastAPI resolves those first. That nesting is the whole idea: dependencies can depend on other dependencies, and the result is a graph that FastAPI walks for you. There is no registration step and no lifetime annotation. Whatever a function needs, it asks for in its parameters.</p>\n<h2>One request, one pass through the graph</h2>\n<p>When a request arrives, FastAPI resolves the graph from the endpoint down. <code class=\"language-text\">create_document</code> needs a user; the user needs a token and settings; the token might need the raw request. FastAPI walks that tree and calls each node.</p>\n<p>The part worth internalizing is caching. Within a single request, FastAPI calls each dependency <strong>once</strong> and reuses the result for every other dependency that asks for it. If <code class=\"language-text\">get_current_user</code> and <code class=\"language-text\">get_db</code> both depend on <code class=\"language-text\">get_settings</code>, <code class=\"language-text\">get_settings</code> runs a single time and both receive the same object. This is on by default; the cache key is the callable plus its arguments.</p>\n<p><img src=\"/57500bb3a0cdab7d64eeec72f44054f5/resolution-graph.svg\" alt=\"A request to POST /documents resolves a dependency graph: the endpoint depends on get_current_user and get_db, get_current_user depends on get_token and get_settings, and get_db also depends on get_settings. The shared get_settings node is called once and cached for the rest of the request, so the second branch receives the same result rather than triggering a second call.\"></p>\n<p>The trap is assuming this cache spans requests. It does not. A dependency that runs “once” runs once <em>per request</em>, and the next request starts clean. So <code class=\"language-text\">get_settings</code> above builds a fresh <code class=\"language-text\">Settings()</code> on every single request, which is wasteful if reading config is expensive. If you want a genuine app-wide singleton, memoize it yourself:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">from</span> functools <span class=\"token keyword\">import</span> lru_cache\n\n<span class=\"token decorator annotation punctuation\">@lru_cache</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">get_settings</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> Settings<span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> Settings<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Now <code class=\"language-text\">get_settings</code> still appears as a normal dependency and can still be overridden in tests, but the object is built once for the life of the process. The <a href=\"https://docs.python.org/3/library/functools.html#functools.lru_cache\"><code class=\"language-text\">lru_cache</code></a> handles the app-scoped part; <code class=\"language-text\">Depends</code> handles the request-scoped wiring. Keeping those two responsibilities separate is most of what “getting DI right” means here.</p>\n<h2><code class=\"language-text\">yield</code> dependencies: setup, hand off, tear down</h2>\n<p>A plain <code class=\"language-text\">return</code> dependency produces a value. A <a href=\"https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/\"><code class=\"language-text\">yield</code> dependency</a> produces a value <em>and</em> a cleanup step, which is exactly what a database session or any other acquired resource needs:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">get_db</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    session <span class=\"token operator\">=</span> SessionLocal<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">try</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">yield</span> session\n    <span class=\"token keyword\">finally</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">await</span> session<span class=\"token punctuation\">.</span>close<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>The function has three parts:</p>\n<ul>\n<li><strong>Setup:</strong> everything before <code class=\"language-text\">yield</code> runs before your handler.</li>\n<li><strong>Hand-off:</strong> the value you <code class=\"language-text\">yield</code> (here, <code class=\"language-text\">session</code>) is what the handler receives.</li>\n<li><strong>Teardown:</strong> everything after <code class=\"language-text\">yield</code> runs at the end.</li>\n</ul>\n<p>The timing of that teardown is the detail people get wrong. The exit code runs <strong>after the response has been sent to the client</strong>, not before. Your handler returns, the bytes go out, and only then does <code class=\"language-text\">session.close()</code> fire.</p>\n<p><img src=\"/07e20d623fd9cc6eb225c253afbd9d9f/yield-lifecycle.svg\" alt=\"A timeline of one request. Before the response line, setup runs outside-in: open span, then open db session, then the handler runs and yields its value. A dashed marker shows the response being sent to the client. After that line, teardown unwinds in reverse order (LIFO): close db session, then close span. A highlighted strip warns that the exit code runs after the response, so an exception there can no longer reach the handler&#x27;s exception handlers, and a background task that outlives the teardown may find the session already closed.\"></p>\n<p>With more than one <code class=\"language-text\">yield</code> dependency, FastAPI unwinds them in reverse order of setup, the way a stack of context managers would: the last one opened is the first one closed. That ordering is a guarantee, not luck. A dependency that opened a transaction inside a dependency that opened a connection tears down in the safe order.</p>\n<p>Two sharp edges come straight out of this timing. Both are documented behavior rather than bugs:</p>\n<ul>\n<li><strong>Exceptions in teardown arrive too late.</strong> An exception raised in the exit code, after <code class=\"language-text\">yield</code>, happens after your response is already out the door, so your route’s exception handlers have run and cannot catch it. Worse, if you wrap the <code class=\"language-text\">yield</code> in <code class=\"language-text\">try/except</code> and swallow the exception without re-raising, <a href=\"https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/\">FastAPI cannot tell an error occurred</a> at all. Catch, log, and re-raise unless you truly mean to bury it.</li>\n<li><strong>Background tasks can race the teardown.</strong> Because teardown fires around the time the response is sent, resources from a <code class=\"language-text\">yield</code> dependency and code running in a <code class=\"language-text\">BackgroundTask</code> can race. Do not assume a database session opened in a <code class=\"language-text\">yield</code> dependency is still open inside a background task. This ordering has shifted across FastAPI releases, so pin down the behavior of the version you actually run before you rely on it.</li>\n</ul>\n<h2>Auth is the dependency that earns its keep</h2>\n<p>The single best use of <code class=\"language-text\">Depends</code> is authentication, because it turns “is this caller allowed?” into one declared input that every protected route shares. In <a href=\"/project/cloud-canvas-ai/\">CloudCanvasAI</a>, a Claude-powered document platform I built, the server verifies a Firebase ID token on every request. That verification is a dependency, not a line repeated in forty handlers:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">async</span> <span class=\"token keyword\">def</span> <span class=\"token function\">require_user</span><span class=\"token punctuation\">(</span>token<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span> <span class=\"token operator\">=</span> Depends<span class=\"token punctuation\">(</span>get_token<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> User<span class=\"token punctuation\">:</span>\n    user <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> verify_firebase_token<span class=\"token punctuation\">(</span>token<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> user <span class=\"token keyword\">is</span> <span class=\"token boolean\">None</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">raise</span> HTTPException<span class=\"token punctuation\">(</span>status_code<span class=\"token operator\">=</span><span class=\"token number\">401</span><span class=\"token punctuation\">,</span> detail<span class=\"token operator\">=</span><span class=\"token string\">\"invalid token\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">return</span> user</code></pre></div>\n<p>When a handler wants the user object, it takes <code class=\"language-text\">user: User = Depends(require_user)</code>. When a route only needs the <em>check</em> and not the return value, attach the dependency at the router so it covers a whole group of routes at once:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\">router <span class=\"token operator\">=</span> APIRouter<span class=\"token punctuation\">(</span>dependencies<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span>Depends<span class=\"token punctuation\">(</span>require_user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>For anything parameterized, like role checks, reach for a class with <code class=\"language-text\">__call__</code>. The instance holds the config (here, the required role), and the call does the work, so you get a reusable, testable guard:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">RequireRole</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__init__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> role<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n        self<span class=\"token punctuation\">.</span>role <span class=\"token operator\">=</span> role\n\n    <span class=\"token keyword\">def</span> <span class=\"token function\">__call__</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">:</span> User <span class=\"token operator\">=</span> Depends<span class=\"token punctuation\">(</span>require_user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> User<span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">if</span> self<span class=\"token punctuation\">.</span>role <span class=\"token keyword\">not</span> <span class=\"token keyword\">in</span> user<span class=\"token punctuation\">.</span>roles<span class=\"token punctuation\">:</span>\n            <span class=\"token keyword\">raise</span> HTTPException<span class=\"token punctuation\">(</span>status_code<span class=\"token operator\">=</span><span class=\"token number\">403</span><span class=\"token punctuation\">,</span> detail<span class=\"token operator\">=</span><span class=\"token string\">\"forbidden\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> user\n\nadmin_only <span class=\"token operator\">=</span> RequireRole<span class=\"token punctuation\">(</span><span class=\"token string\">\"admin\"</span><span class=\"token punctuation\">)</span></code></pre></div>\n<h2>Testing is the reason this pays off</h2>\n<p>Here is the seam that inline construction never gives you. FastAPI lets you <a href=\"https://fastapi.tiangolo.com/advanced/testing-dependencies/\">override any dependency</a> at test time without touching a single handler:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">from</span> myapp <span class=\"token keyword\">import</span> app<span class=\"token punctuation\">,</span> get_db\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">override_get_db</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">yield</span> test_session\n\napp<span class=\"token punctuation\">.</span>dependency_overrides<span class=\"token punctuation\">[</span>get_db<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> override_get_db</code></pre></div>\n<p>Every route that depends on <code class=\"language-text\">get_db</code>, directly or three levels deep, now receives the test session instead of the real one. There is no monkeypatching of module globals and no conditional branch for a test mode. The override map is keyed by the original callable, so the same trick swaps out <code class=\"language-text\">require_user</code> for a fixture that returns a canned user, or <code class=\"language-text\">get_settings</code> for a config that points at a throwaway database.</p>\n<p>This is why declaring dependencies beats constructing them inline. The declaration gives you a named place to reach in and substitute; inline construction gives you nothing to grab.</p>\n<h2>Where it breaks</h2>\n<p><strong>Sync dependencies and the event loop.</strong> A dependency written as <code class=\"language-text\">def</code>, not <code class=\"language-text\">async def</code>, runs in an external threadpool, exactly like a sync path operation, which keeps it from blocking the loop. But the threadpool is finite, so a sync dependency doing slow blocking I/O on a hot path will consume workers and starve everything else, the same failure I wrote about for <a href=\"/blog/2026-07-10-fastapi-blocking-event-loop/\">blocking the FastAPI event loop</a>. If a dependency does real I/O, make it <code class=\"language-text\">async</code> and use an async client; if it must stay sync, keep it cheap.</p>\n<p><strong>Treating request scope as app scope.</strong> The most common performance bug I see is a dependency that constructs an expensive client (an HTTP session, a model handle, a connection) on every request, because it lives in a plain <code class=\"language-text\">Depends</code>. Per-request is the default, and per-request construction of a shared resource is pure overhead. App-lifetime objects belong in the <a href=\"https://fastapi.tiangolo.com/advanced/events/\">lifespan</a> handler and get read out of application state, not rebuilt per call.</p>\n<p><strong>Silent cache surprises.</strong> Per-request caching is usually what you want, but if a dependency has side effects and you expected it to run twice in one request, it will run only once. Set <code class=\"language-text\">Depends(dep, use_cache=False)</code> when you genuinely need a fresh call each time. This is rare, and reaching for it is often a sign the logic wants to be plain code inside the handler instead.</p>\n<p><strong>Over-broad dependencies.</strong> A <code class=\"language-text\">Depends</code> that quietly does three unrelated things (verifies auth, opens a transaction, and logs) is hard to override in a test, because you cannot replace one part without replacing all three. Keep each dependency to one job. Small dependencies compose; fat ones fight you.</p>\n<h2>Tradeoffs, and what I would keep in mind</h2>\n<p>FastAPI’s dependency system is a request-scoped resolver with per-request caching and stack-ordered cleanup. That is a smaller thing than a full inversion-of-control container (the kind of framework that creates and wires every object for you), and the mistake is trying to grow it into one. It has no notion of scoped, transient, and singleton lifetimes. There is request scope, and there is whatever you memoize yourself for the process.</p>\n<p>Once you stop expecting the missing features, the model is clean:</p>\n<ul>\n<li>parameters declare needs,</li>\n<li><code class=\"language-text\">yield</code> handles cleanup in the right order, and</li>\n<li><code class=\"language-text\">dependency_overrides</code> gives every layer a test seam.</li>\n</ul>\n<p>For the backends I build, that is enough, and I would not reach for more. Archi authenticates every operator request and hands it a scoped session through exactly this mechanism; CloudCanvasAI verifies a token on every call the same way. The plumbing stays boring and out of the handlers, which is the point: let the framework build what each request needs, so the endpoint is left to do the one thing it exists to do.</p>\n<hr>\n<p><em>Diagrams by M. Hassan Ahmed, created for this post and released under CC0 (public domain). No external image was used.</em></p>","frontmatter":{"title":"How FastAPI Dependency Injection Actually Works","date":"2026-08-06T00:00:00.000Z","description":"A practical guide to FastAPI dependency injection: how Depends resolves a graph, yield setup and teardown, per-request caching, and where it leaks.","thumbnail":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAAB30lEQVQozy2Q6Y6bMBSF+TFqAtgGbLAxO4Q1kJUsk0VKmnamo7bTef+36SWJ9Onq2NfnLlas8EDDI/F3UftWrv9W/b9m+1WtP9PZRzr7Vaz+TBa/s/kHUPWfXvnd8PdgeaDohq8ZPqZRUq6bxbFdnheby3RxLtt91e2b+XG6PEGsutfF5ppVPTIDeK/fUXQr0mmEWYxorJmRChiQDkA/sZ6oRoRogmkyWAZixeC56ZZU1pZbAoQliKaI5boZ3Am1+2gq8YAxlo8jVIeU4pW3sv8S+c2MzqJ4N4Mt5h0WHXaqYTwzNEUJMFk7/tT2GmxnxJkAUFohvLHkDPMpchrCW51mKpEqFohlyC4NXrnZTk62k+5Sr37ks4tINna4tLxOt0JFRa6KhEZcADyDMDwANBRi2dXNXuPmWCzfJ/OffnEIigNPelNO751ZTGz4gxDT0HBSiCqW8GfD2iwnooY+PFnJbCvSnvozuDHd1pQNLKXY8SGob155ddITi09EdIi3yIHCsYq4bniwJ5WVJXJb1lSWVBS2V1s814insLAPq0tUX5x4bwY75NQqHuYHJwiNSOaCraBiwmTBZG7xDATMC2+Ubxp7GVsvYwpipA+MEb8jHmKk87F+F8gZoad4pP4DMNpGYwqfMVAAAAAASUVORK5CYII=","aspectRatio":1.899441340782123,"src":"/static/df58b502e7deb0a3d25d6eacd0c715a6/40a76/hero.png","srcSet":"/static/df58b502e7deb0a3d25d6eacd0c715a6/c972b/hero.png 340w,\n/static/df58b502e7deb0a3d25d6eacd0c715a6/27625/hero.png 680w,\n/static/df58b502e7deb0a3d25d6eacd0c715a6/40a76/hero.png 1360w,\n/static/df58b502e7deb0a3d25d6eacd0c715a6/ed396/hero.png 2000w","sizes":"(max-width: 1360px) 100vw, 1360px"}}}}}},"pageContext":{"slug":"/2026-08-06-fastapi-dependency-injection-explained/","previous":"blog/2026-08-07-argocd-applicationsets-many-clusters/","next":"blog/2026-08-10-async-connection-pooling-fastapi/"}},"staticQueryHashes":["32046230"]}