Fix RuntimeError: RuntimeError: No response returned from ASGI application in FastAPI
This error occurs when custom middleware does not properly call the next middleware or return a response, breaking the ASGI chain. Fix it by ensuring your middleware calls call_next(request) and returns the response object. If using BaseHTTPMiddleware, the dispatch method must return the response from call_next.
Reading the Stack Trace
Here's what each line means:
- File "/app/src/middleware/logging_middleware.py", line 15, in dispatch: The custom middleware logs the request but does not call call_next() to pass the request to the next handler in the chain.
- File "/app/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 68, in __call__: Starlette's BaseHTTPMiddleware expects the dispatch method to return a Response, but received None.
- RuntimeError: No response returned from ASGI application: The ASGI server received no response because the middleware broke the request/response chain.
Common Causes
1. Not calling call_next in dispatch
The middleware dispatch method performs some logic but forgets to call call_next(request) to pass the request to the next handler.
from starlette.middleware.base import BaseHTTPMiddleware
import logging
logger = logging.getLogger(__name__)
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
logger.info(f"Request: {request.method} {request.url}")
# Missing: response = await call_next(request)
# Missing: return response
2. Not returning the response from dispatch
The middleware calls call_next but does not return the response, so Starlette receives None.
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
logger.info(f"Duration: {duration:.3f}s")
# Missing: return response
3. Exception in middleware before call_next
The middleware raises an exception before reaching call_next, and there is no error handling to return a proper response.
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
api_key = request.headers["X-API-Key"] # KeyError if missing
if not validate_key(api_key):
raise ValueError("Invalid key")
response = await call_next(request)
return response
The Fix
Ensure the dispatch method always calls await call_next(request) to pass the request down the middleware chain and always returns the resulting Response object. Adding a return type annotation helps catch this mistake during development.
from starlette.middleware.base import BaseHTTPMiddleware
import logging
logger = logging.getLogger(__name__)
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
logger.info(f"Request: {request.method} {request.url}")
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
import logging
import time
logger = logging.getLogger(__name__)
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next) -> Response:
start = time.time()
logger.info(f"Request: {request.method} {request.url}")
response = await call_next(request)
duration = time.time() - start
response.headers["X-Process-Time"] = f"{duration:.3f}"
logger.info(f"Response: {response.status_code} in {duration:.3f}s")
return response
Testing the Fix
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_middleware_returns_response():
response = client.get("/health")
assert response.status_code == 200
def test_middleware_adds_process_time_header():
response = client.get("/health")
assert "x-process-time" in response.headers
assert float(response.headers["x-process-time"]) >= 0
def test_middleware_does_not_break_post_requests():
response = client.post("/api/data", json={"key": "value"})
assert response.status_code in (200, 201, 422)
assert "x-process-time" in response.headers
def test_middleware_handles_404():
response = client.get("/nonexistent")
assert response.status_code == 404
assert "x-process-time" in response.headers
Run your tests:
pytest tests/test_middleware.py -v
Pushing Through CI/CD
git checkout -b fix/fastapi-middleware,git add src/middleware/logging_middleware.py tests/test_middleware.py,git commit -m "fix: add call_next and return response in logging middleware dispatch",git push origin fix/fastapi-middleware
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
- run: pytest --tb=short -q
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the root cause
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
pip install bugstack
Step 2: Initialize
import bugstack
bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, verified fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Run the test suite locally to confirm middleware passes requests through correctly.
- Open a pull request with the middleware fix.
- Wait for CI checks to pass on the PR.
- Have a teammate review and approve the PR.
- Merge to main and verify all endpoints respond correctly in staging.
Frequently Asked Questions
BugStack tests that all endpoints return valid responses through the middleware, checks for the expected custom headers, and runs your full test suite to confirm no requests are dropped.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review the middleware changes before merging.
BaseHTTPMiddleware is easier to write but has limitations with streaming responses. For advanced use cases like streaming or WebSocket handling, implement pure ASGI middleware instead.
Middleware executes in the order it is added with app.add_middleware(). The last added middleware runs first for incoming requests and last for outgoing responses, like a stack.