stryv.ai

Modernizing .NET APIs to Serverless Python: How we migrated our legacy system with 300-400 APIs to Serverless Architecture

This blog details our journey migrating a legacy .NET monolithic backend powering 300–400 APIs to a modern, serverless Python architecture using FastAPI and Azure Function Apps. Initially considering Node.js, we ultimately chose Python for its team familiarity, concise syntax, and rich ecosystem, with FastAPI offering async capabilities, built-in validation, and modular routing. A key challenge was Azure’s “one function per route” limitation, which we solved by bundling APIs into modular FastAPI apps, reducing hundreds of functions to fewer than 10. We enhanced scalability and performance through Redis caching, async processing, Docker-based deployments, Kubernetes orchestration, and CI/CD pipelines. The migration improved performance, simplified management, and future-proofed the architecture while boosting developer productivity.

Our legacy systems were initially built on .NET APIs are monolithic. But they were the digital backbone of our organization. As designed to power up critical business functions, it has backend supporting hundreds of APIs that handle everything from core business logic to data processing and external integrations. Growing business led to a spike in traffic and as these APIs started to face increasing demand, the need for more frequent updates to support new business features has increased. While the .NET framework is known for its reliability and stability, it does not fit the growing business needs, especially when it comes to handling cloud-native workloads.

Modernizing legacy was the only option left. But migrating the codebase built on .NET APIs to serverless architecture is no small feat. When compared to the code running on traditional servers, to the one that runs on cloud, the cloud providers can manage resources dynamically. Enhances your system’s scalability and makes it cost-efficient. Most importantly you have to pay for what you use. As the legacy system had a large number of APIs (300-400), which is quite a lot to migrate. These APIs are responsible for providing business logic and interacting with data, so moving them without affecting the system’s functionality is definitely challenging.

Let’s walkthrough into our blog and know our end-to-end experience, from initial technology choices to final architecture, including key lessons and optimizations we implemented along the way.

The Challenges We Encountered While Migrating From .NET Monolith to Modern Backend

As our legacy was built on .NET transforming it was our primary intention. The backend includes 300-400 APIs roughly, supports both data operations and crucial business logic. The codebase is monolithic and has scalability constraints, which makes re-architecture a crucial part.

Our intentions were clear:  

  • Firstly, Modernize the tech stack, to adopt a language and framework better suited to cloud-native and serverless environments.  
  • Improve developer productivity and onboarding through cleaner, more maintainable code. 
  • Enable asynchronous programming and scalable patterns to handle potentially high loads. 
  • Simplify deployment and operational complexity by optimizing how we package and deploy these multiple APIs to cloud platforms.  

Why Node.js was the First Choice?

Our initial thought was to use Node.js for the backend rewrite for several reasons:  

  • Industry popularity and momentum: Node.js reigns as the de-facto language for serverless functions, with seamless support on AWS Lambda, Azure Function Apps, and Google Cloud Functions.  
  • Asynchronous, non-blocking architecture: It’s capability to handle multiple requests concurrently, makes it a perfect opt for event-driven serverless workloads.  
  • Dynamic Tech Stack: Includes abundant tutorials, templates, and community-maintained libraries.  

Despite these attractive points, we encountered notable issues in practice:  

  • Our team’s stronger expertise relies in Python rather than JavaScript/TypeScript.  
  • Mapping complex .NET business logic especially data-heavy and computation-intensive routines felt unnatural in Node.js.  
  • Onboarding new developers was slower due to unfamiliarity.  
  • Some Python-specific libraries and tools offered clear advantages for our planned workload, including data manipulation and REST API integrations.  

Modernizing .NET APIs to Serverless Python: What is the Reason of Switch to Python and FastAPI?

Python emerged as the natural successor for multiple reasons:  

  • Readable and concise syntax: Python code closely mirrored the original .NET logic, improving maintainability.  
  • Rich ecosystem: From HTTP clients (requests) to data processing (pandas) to machine learning libraries, Python already had mature tools we could rely on.  
  • Developer productivity: Python’s clarity resulted in faster prototyping and fewer bugs. 
  • Developer familiarity: Our team was more comfortable and confident in Python.  
  • FastAPI framework: Among Python web frameworks, FastAPI stood out for serverless readiness.  

Benefits of FastAPI

Building APIs with FastAPI offered compelling benefits compared to traditional Python web frameworks: 

  • Asynchronous programming: Native async/await capabilities enabled high concurrency, comparable to Node.js for I/O-bound tasks.  
  • Automatic interactive documentation: Integrated Swagger UI and ReDoc helped immensely with debugging and collaboration.  
  • Data validation: Leveraging Python type hints, FastAPI auto-validated input/output, reducing runtime errors.  
  • Modular architecture and routing: FastAPI routers made it possible to organize APIs cleanly, which was vital as API count grew.  
  • Lightweight yet powerful: Perfect for microservices and serverless function implementations. 

The Challenge: Deployment on Azure Function Apps

Deploying hundreds of APIs into Azure Function Apps introduced a new set of challenges.

The Core Issue: One Function per Route

By default, Azure Function Apps require you to define a separate function for each HTTP route. With around 300-400 APIs, this would mean creating the corresponding number of individual functions, which is:  

  • Challenging from a management perspective: Deploying, configuring, and debugging hundreds of individual Azure Functions quickly became impractical.  
  • Costly in terms of cold start latency: More functions can amplify startup delays. 
  • Cumbersome for monitoring and scaling: Tracking performance and logs for hundreds of separate functions increased operational overhead.  

 

Our Solution: Using FastAPI as a Proxy in Function Apps

We devised an elegant approach:  

  • Bundle related APIs into module-based FastAPI applications.  
  • Expose each FastAPI application inside a single Azure Function App instance.  
  • Each Function App acts like a proxy or gateway, exposing hundreds of routes internally via FastAPI’s routing but appearing as a single function from Azure’s perspective.  

This had multiple benefits:  

  • Reduction from hundreds of functions to only 7-8 function apps, each handling dozens or hundreds of APIs internally.  
  • Simplified deployment and operations: Fewer Azure functions meant easier monitoring, security management, and scale tuning.  
  • Code clarity: Business logic grouped by module inside respective FastAPI apps made the codebase cleaner and easier to maintain.  
  • On-demand expansion: Adding new APIs became a matter of updating FastAPI routes, not recreating or redeploying Azure functions.  
  • Cold start improvements: Dependencies and runtime initialization happened once per module app, reducing cold start impact.  

Optimization, Scalability, and Performance Enhancements

To support scalability and performance, we implemented complementary strategies:

Redis Caching  

  • Cached frequently accessed, costly computations e.g., API responses, preprocessed data, and ML inference results.  
  • It reduces pressure on databases and external APIs.  
  • Delivers Faster response times during high load or bursts traffic.  

Async Efficiency with FastAPI  

  • Leveraged FastAPI’s async/await to maximize concurrency and minimize blocking. 
  • Efficiently handled multiple simultaneous API requests.  

Containerization and Horizontal Scaling  

  • Deployed FastAPI apps via Docker containers.  
  • Used orchestration platforms like Kubernetes to scale pods dynamically.  
  • Configured multi-worker Gunicorn + Uvicorn servers for enhanced throughput.  

Deployment and Monitoring Improvements  

  • Integrated CI/CD pipelines for smooth deployment.  
  • Employed distributed tracing and centralized logging to detect bottlenecks and errors swiftly.  

Developer Experience and Troubleshooting

Local emulation: Using AWS SAM CLI and Chalice, we tested Azure-like serverless environments locally to catch issues early.

Error handling: Python’s familiar stack traces and FastAPI validation reduced debugging time.
Auto docs: Swagger UI significantly improved debugging and API exploration both in dev and QA cycles.

Key Outcomes: What We Achieved

Successful legacy modernization: The .NET backend was fully replaced with modular, scalable Python FastAPI apps.

Management simplicity: From potentially hundreds of Azure Functions, reduced to fewer than 10 per environment.

Improved performance: FastAPI + Redis caching provided responsive APIs even under load.

Maintainability and team morale: Cleaner, modular codebases and familiar tools boosted developer productivity.

Future-proof architecture: Easy to extend, scale, and secure.

Final Thoughts: Lessons from Our Migration

  • Python with FastAPI is fully serverless-ready, blending developer productivity with performance. 
  • Legacy migration is not just code translation — architecture, team skills, and deployment considerations weigh heavily. 
  • Limitations of Azure Function Apps can be elegantly circumvented by integrating modular FastAPI applications inside function apps. 
  • Optimization is multi-faceted: Utilize caching, async programming, container orchestration, and good CI/CD practices together. 
  • Developer experiences matter: Auto-generated docs, local testing, and readable errors accelerate delivery cycles. 

If your team is facing a similar problem during modernization of legacy applications, or you need to manage hundreds of APIs in cloud serverless infrastructure, consider Python + FastAPI + Redis combo with intelligent Azure deployment strategies. It’s a scalable, maintainable, and future-friendly approach that worked well for us, and we hope sharing this helps you avoid common pitfalls and accelerate your journey. 

Thank you for reading! If you would like to dive deeper into any aspect of the migration, Python serverless development, or Azure Function App design, feel free to reach out. 

Share this Article: