Recommended resources
The freely available resources this lesson will direct you to.
- Python FastAPI Tutorial (Part 7): Sync vs Async - Converting Your App to Asynchronouswww.youtube.com
- Claude Code (New Updates): New SIMPLIFY & BATCH Skill Agents, Better UX, New Commands & MORE!www.youtube.com
- Hands-On with Claude Code: Automating Git Workflows and Legacy ...www.sitepoint.com
- How to refactor 50k lines of legacy code without breaking prod using ...www.reddit.com
Hello! Welcome back to our series on advanced Claude Code workflows.
In our last session, you meticulously planned a sync-to-async refactoring for a FastAPI endpoint. You performed a detailed cross-file dependency analysis and created a sequential blueprint for the conversion. That was the "measure twice" phase—strategic, analytical, and crucial for de-risking the change.
Today, we "cut once." The learning outcome for this lesson is to guide Claude Code through an iterative refactor of a synchronous API route and its service functions to async equivalents. You'll take the plan you built and execute it, not with a single, risky command, but through a controlled, step-by-step process. This method of incremental refactoring with continuous verification is a cornerstone of professional AI-assisted development, ensuring each change is correct before moving to the next.
The Philosophy: Iterative Refactoring as a Dialogue
Why not just tell Claude, "Convert my FastAPI app to async"? As you know from your experience, large-scale, context-heavy tasks can cause AI assistants to fail if not managed properly. A more robust approach is to break the problem down and guide the agent through each step.
This aligns perfectly with Claude Code's internal process, which can be visualized as an iterative loop.

By feeding Claude one task at a time and verifying the result, you act as the "human-in-the-loop," ensuring the refactoring stays on track. This incremental method is not slower; it's faster because it prevents the costly process of untangling large, failed changes.
Let's explore the practical framework for this approach.
How to refactor 50k lines of legacy code without breaking prod
The following Reddit post, 'How to refactor 50k lines of legacy code without breaking prod,' offers a powerful real-world perspective on this exact workflow. It emphasizes the importance of control and verification.
Read the sections Step 2: Set Up Your CLAUDE .md File and Step 3: Incremental Refactoring With Continuous Verification. Focus on the core idea: breaking a large refactor into small, specific tasks, each with its own prompt and verification step. The prompt structure provided is a great template: "Implement this refactoring step: [specific task]. After making changes, run..."
Executing the Refactoring Plan: A Step-by-Step Guide
Now, let's execute the plan you created in the previous lesson. We will follow the sequence of changes required for a complete sync-to-async conversion in FastAPI. For each step, you will provide a precise prompt to Claude, review its changes, and then proceed.
The technical details of what needs to change are covered extensively in the video you reviewed previously. We'll use those details to craft our prompts.
Step 1: Refactor the Database Layer
The foundation of our async conversion is an async-aware database connection.
Your Prompt to Claude:
"Let's begin the sync-to-async refactoring based on our plan. Start withdatabase.py.
- Update the
SQLALCHEMY_DATABASE_URLto use an async driver (e.g.,postgresql+asyncpgorsqlite+aiosqlite).- Import
create_async_engine,async_sessionmaker, andAsyncSessionfromsqlalchemy.ext.asyncio.- Replace
create_enginewithcreate_async_engine.- Replace
sessionmakerwithasync_sessionmaker, configuring it forAsyncSessionand settingexpire_on_commit=False.- Convert the
get_dbdependency into anasyncgenerator. It should useasync withon the session factory to yield anAsyncSessioninstance."
After Claude makes the changes, use the diff view to confirm they are correct before moving on.
Step 2: Implement the Application Lifespan
FastAPI needs an async-aware way to handle startup and shutdown events, like creating database tables.
Your Prompt to Claude:
"Next, modifymain.py. Remove the synchronousBase.metadata.create_all(bind=engine)call.
- Import
asynccontextmanagerfromcontextlib.- Define an
async deffunction decorated with@asynccontextmanagercalledlifespan.- Before the
yield, create an async connection withasync with engine.begin() as conn:and run the synchronous table creation withawait conn.run_sync(Base.metadata.create_all).- After the
yield, dispose of the engine withawait engine.dispose().- Assign this function to the app:
app = FastAPI(lifespan=lifespan)."
This pattern ensures database setup happens within the async event loop.
Step 3: Refactor the Service and Repository Layers
This is the core of the logic change. Here, precision is key, especially regarding eager loading.
Python FastAPI Tutorial (Part 7): Sync vs Async - Converting Your App to Asynchronous
As a quick refresher, let's revisit the most critical and often-missed part of an async SQLAlchemy conversion: eager loading. Lazy loading is not supported and will cause runtime errors.
Briefly re-watch the segment from 13:05 to 14:44. The explanation of why lazy loading fails and how selectinload solves it is fundamental to crafting the next prompt.
Now, let's apply this knowledge.
Your Prompt to Claude:
"Now, refactor the service function that fetches a post.
- Change the function signature to
async def.- The call to the database execution method (e.g.,
session.execute(...)) must now beawaited.- Based on our analysis, the
Postmodel has related objects that are accessed. Modify the SQLAlchemyselectstatement to include.options(selectinload(models.Post.relationship_name))to eagerly load these relationships.- Remember to
awaitcalls todb.commit(),db.refresh(), anddb.delete(), but do notawaitdb.add()."
The prompt for this step is a great example of combining your domain knowledge (FastAPI/SQLAlchemy) with the refactoring plan to give Claude precise, executable instructions. The SitePoint article Hands-On with Claude Code demonstrates a similar incremental extraction process in its case study.
Step 4: Refactor the API Layer (Route Handler)
With the service layer now async, the final step is to update the API route that calls it.
Your Prompt to Claude:
"Finally, update the API route handler forGET /posts/{post_id}in the router file.
- Change the function signature to
async def.- The call to the underlying service function must now be
awaited."
After this final change, your endpoint is fully asynchronous from the API entry point down to the database!
An Alternative: The /batch Command for Parallelizable Tasks
The iterative method is perfect for complex, sequential refactors. However, for tasks that are highly parallelizable and have few conflicts, Claude Code offers a more automated tool: the /batch skill.
Claude Code (New Updates): New SIMPLIFY & BATCH Skill Agents, Better UX, New Commands & MORE!
To broaden your toolkit, let's look at the /batch skill, which is designed for large-scale, parallel migrations.
Watch from 05:34 to 07:17. Note how /batch works by splitting tasks across multiple agents, each with its own isolated work tree. This is ideal for tasks like converting file formats or upgrading libraries across many independent components.
For example, a prompt like /batch migrate from CommonJS to ES modules in src/components would be a great use of this skill. For our sync-to-async conversion, which involves a deep, interconnected chain of dependencies, the iterative approach provides more control and is generally more suitable.
Conclusion
You have successfully executed a complex refactoring task by guiding Claude Code through a structured, iterative process. This workflow maximizes your control and minimizes risk, allowing you to leverage AI for significant codebase improvements safely.
Key Takeaways:
- Iterative Execution is Key: Break down complex refactors into small, verifiable steps. This aligns with Claude's reasoning loop and prevents large-scale failures.
- Prompt with Precision: Combine your domain expertise (e.g., knowing about
selectinload) with the refactoring plan to give Claude unambiguous instructions for each step. - The
Review -> Verify -> CommitCycle: Treat Claude Code as a pair programmer. Review every change it makes, verify its correctness, and commit before moving on. - Choose the Right Tool: Use the iterative method for deep, sequential changes and consider the
/batchskill for wide, parallelizable tasks.
Next Up:
You've made the changes and reviewed the code diff. But the ultimate verification comes from testing. In the next lesson, we will complete the refactoring loop by adapting our test suite. You will learn how to direct Claude Code to update corresponding unit tests to validate the migrated asynchronous API route, ensuring your refactored code is not only clean but also correct.