diff --git a/examples/miniapps/starlette-lifespan/README.rst b/examples/miniapps/starlette-lifespan/README.rst
new file mode 100644
index 00000000..c6d1b2b4
--- /dev/null
+++ b/examples/miniapps/starlette-lifespan/README.rst
@@ -0,0 +1,39 @@
+Integration With Starlette-based Frameworks
+===========================================
+
+This is a `Starlette `_ +
+`Dependency Injector `_ example application
+utilizing `lifespan API `_.
+
+.. note::
+
+ Pretty much `any framework built on top of Starlette `_
+ supports this feature (`FastAPI `_,
+ `Xpresso `_, etc...).
+
+Run
+---
+
+Create virtual environment:
+
+.. code-block:: bash
+
+ python -m venv env
+ . env/bin/activate
+
+Install requirements:
+
+.. code-block:: bash
+
+ pip install -r requirements.txt
+
+To run the application do:
+
+.. code-block:: bash
+
+ python example.py
+ # or (logging won't be configured):
+ uvicorn --factory example:container.app
+
+After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
+etc).
diff --git a/examples/miniapps/starlette-lifespan/example.py b/examples/miniapps/starlette-lifespan/example.py
new file mode 100755
index 00000000..11a31e61
--- /dev/null
+++ b/examples/miniapps/starlette-lifespan/example.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+from logging import basicConfig, getLogger
+
+from dependency_injector.containers import DeclarativeContainer
+from dependency_injector.ext.starlette import Lifespan
+from dependency_injector.providers import Factory, Resource, Self, Singleton
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import JSONResponse
+from starlette.routing import Route
+
+count = 0
+
+
+def init():
+ log = getLogger(__name__)
+ log.info("Inittializing resources")
+ yield
+ log.info("Cleaning up resources")
+
+
+async def homepage(request: Request) -> JSONResponse:
+ global count
+ response = JSONResponse({"hello": "world", "count": count})
+ count += 1
+ return response
+
+
+class Container(DeclarativeContainer):
+ __self__ = Self()
+ lifespan = Singleton(Lifespan, __self__)
+ logging = Resource(
+ basicConfig,
+ level="DEBUG",
+ datefmt="%Y-%m-%d %H:%M",
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
+ )
+ init = Resource(init)
+ app = Factory(
+ Starlette,
+ debug=True,
+ lifespan=lifespan,
+ routes=[Route("/", homepage)],
+ )
+
+
+container = Container()
+
+if __name__ == "__main__":
+ import uvicorn
+
+ uvicorn.run(
+ container.app,
+ factory=True,
+ # NOTE: `None` prevents uvicorn from configuring logging, which is
+ # impossible via CLI
+ log_config=None,
+ )
diff --git a/examples/miniapps/starlette-lifespan/requirements.txt b/examples/miniapps/starlette-lifespan/requirements.txt
new file mode 100644
index 00000000..966c2bb8
--- /dev/null
+++ b/examples/miniapps/starlette-lifespan/requirements.txt
@@ -0,0 +1,3 @@
+dependency-injector
+starlette
+uvicorn
diff --git a/src/dependency_injector/ext/starlette.py b/src/dependency_injector/ext/starlette.py
new file mode 100644
index 00000000..9498a3d9
--- /dev/null
+++ b/src/dependency_injector/ext/starlette.py
@@ -0,0 +1,53 @@
+import sys
+from abc import ABCMeta, abstractmethod
+from typing import Any, Callable, Coroutine, Optional
+
+if sys.version_info >= (3, 11): # pragma: no cover
+ from typing import Self
+else: # pragma: no cover
+ from typing_extensions import Self
+
+from dependency_injector.containers import Container
+
+
+class Lifespan:
+ """A starlette lifespan handler performing container resource initialization and shutdown.
+
+ See https://www.starlette.io/lifespan/ for details.
+
+ Usage:
+
+ .. code-block:: python
+
+ from dependency_injector.containers import DeclarativeContainer
+ from dependency_injector.ext.starlette import Lifespan
+ from dependency_injector.providers import Factory, Self, Singleton
+ from starlette.applications import Starlette
+
+ class Container(DeclarativeContainer):
+ __self__ = Self()
+ lifespan = Singleton(Lifespan, __self__)
+ app = Factory(Starlette, lifespan=lifespan)
+
+ :param container: container instance
+ """
+
+ container: Container
+
+ def __init__(self, container: Container) -> None:
+ self.container = container
+
+ def __call__(self, app: Any) -> Self:
+ return self
+
+ async def __aenter__(self) -> None:
+ result = self.container.init_resources()
+
+ if result is not None:
+ await result
+
+ async def __aexit__(self, *exc_info: Any) -> None:
+ result = self.container.shutdown_resources()
+
+ if result is not None:
+ await result
diff --git a/tests/unit/ext/test_starlette.py b/tests/unit/ext/test_starlette.py
new file mode 100644
index 00000000..e569a382
--- /dev/null
+++ b/tests/unit/ext/test_starlette.py
@@ -0,0 +1,41 @@
+from typing import AsyncIterator, Iterator
+from unittest.mock import ANY
+
+from pytest import mark
+
+from dependency_injector.containers import DeclarativeContainer
+from dependency_injector.ext.starlette import Lifespan
+from dependency_injector.providers import Resource
+
+
+class TestLifespan:
+ @mark.parametrize("sync", [False, True])
+ @mark.asyncio
+ async def test_context_manager(self, sync: bool) -> None:
+ init, shutdown = False, False
+
+ def sync_resource() -> Iterator[None]:
+ nonlocal init, shutdown
+
+ init = True
+ yield
+ shutdown = True
+
+ async def async_resource() -> AsyncIterator[None]:
+ nonlocal init, shutdown
+
+ init = True
+ yield
+ shutdown = True
+
+ class Container(DeclarativeContainer):
+ x = Resource(sync_resource if sync else async_resource)
+
+ container = Container()
+ lifespan = Lifespan(container)
+
+ async with lifespan(ANY) as scope:
+ assert scope is None
+ assert init
+
+ assert shutdown