<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shuttle</title>
    <description>The latest articles on DEV Community by Shuttle (@shuttle).</description>
    <link>https://dev.to/shuttle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1144602%2F17e688cd-ef4e-43c8-b084-8f8f8f5780d6.png</url>
      <title>DEV Community: Shuttle</title>
      <link>https://dev.to/shuttle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shuttle"/>
    <language>en</language>
    <item>
      <title>Shuttle MCP Server - Deploy Your App with a Prompt</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Wed, 08 Oct 2025 14:25:35 +0000</pubDate>
      <link>https://dev.to/shuttle/shuttle-mcp-server-deploy-your-app-with-a-prompt-i1d</link>
      <guid>https://dev.to/shuttle/shuttle-mcp-server-deploy-your-app-with-a-prompt-i1d</guid>
      <description>&lt;p&gt;AI agents are changing how we build software, and the Model Context Protocol (MCP) is at the heart of this shift. MCP lets AI agents connect with external tools and APIs - kind of like giving your AI assistant hands to actually do things instead of just talking about them.&lt;/p&gt;

&lt;p&gt;We've updated our Shuttle MCP server, and it's a proper workflow upgrade. Here's what's new.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does the Shuttle MCP do?
&lt;/h2&gt;

&lt;p&gt;The Shuttle MCP server gives AI agents direct access to Shuttle's platform and documentation. Your AI agent can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search through Shuttle documentation to understand how things work, ensuring you have the latest information&lt;/li&gt;
&lt;li&gt;Deploy projects directly to Shuttle&lt;/li&gt;
&lt;li&gt;List all your Shuttle projects&lt;/li&gt;
&lt;li&gt;Get detailed information about specific projects&lt;/li&gt;
&lt;li&gt;View deployment logs in real-time for debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of copy-pasting commands and switching between your terminal, documentation, and AI chat, your agent handles the entire workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With the Previous Version
&lt;/h2&gt;

&lt;p&gt;The first version of our MCP server worked well for most users. The tools were functional, AI agents could call them, and they'd return the expected data for common workflows.&lt;/p&gt;

&lt;p&gt;But we noticed struggles in edge cases. When dealing with unusual deployment configurations or error states, agents would sometimes call the wrong tool or miss required parameters. When something failed in these scenarios, they'd get stuck because error messages didn't provide enough context to self-correct.&lt;/p&gt;

&lt;p&gt;We realized the problem wasn't the tools themselves as they were powerful enough - it was how we presented them to the agents. In edge cases, agents didn't have enough guidance to understand when or how to use each tool. We'd built powerful functionality but needed to be documented better for AI agents to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New in This Version
&lt;/h2&gt;

&lt;p&gt;The updated server doesn't add flashy new features - it makes the existing ones actually work the way they should. The Agents now have much higher success rates, and even when things go wrong, they know how to recover and fix issues on their own.&lt;/p&gt;

&lt;p&gt;We've enhanced how AI agents understand Shuttle. Each MCP tool includes detailed instructions that help agents understand not just what a tool does, but when and how to use it.&lt;/p&gt;

&lt;p&gt;We've also reworked error handling. When something goes wrong now, the error messages are designed for AI agents - they provide enough context and guidance for the agent to understand what happened and how to fix it. Instead of getting stuck, agents can self-correct and try again with a high chance of success.&lt;/p&gt;

&lt;p&gt;With this, your AI agent works faster and makes fewer mistakes. It understands deployment workflows and can troubleshoot issues on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Learned About Building MCP Servers
&lt;/h2&gt;

&lt;p&gt;Building MCP tools isn't just about writing the code that exposes functionality. It's about documentation and context - specifically, documentation written for AI models rather than humans.&lt;/p&gt;

&lt;p&gt;A powerful tool isn't very efficient if the agent doesn't know when and how to use it. You need to provide condensed, structured context that models can actually parse and understand. Otherwise, you're handing your agent a toolbox without labels on any of the tools leaving the agent to guess what to do with it.&lt;/p&gt;

&lt;p&gt;This means thinking differently about how you write tool descriptions, parameter explanations, and error messages. Every piece of text needs to be optimized for model comprehension, not human readability.&lt;/p&gt;

&lt;p&gt;If you're building your own MCP server, spend as much time on how you describe your tools as you do on implementing them. The quality of that documentation directly determines whether AI agents can actually use what you've built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Benefits
&lt;/h2&gt;

&lt;p&gt;Here's where this gets practical. You can now tell your AI agent "Deploy my Shuttle App" and it handles everything using the Shuttle MCP server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a project for you if you don't have one already&lt;/li&gt;
&lt;li&gt;Deploys the project to the cloud&lt;/li&gt;
&lt;li&gt;Handles edge cases&lt;/li&gt;
&lt;li&gt;Catches and fixes common deployment issues automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire workflow - from project creation to a live deployment - happens through your AI agent.&lt;/p&gt;

&lt;p&gt;We tested this with Claude Sonnet 4.5 to build a complete Rust API from scratch, you can see the MCP server in action. &lt;a href="https://www.shuttle.dev/blog/2025/10/01/build-rust-api-sonnet-4-5?utm_source=shuttle_blog&amp;amp;utm_campaign=shuttle_mcp_update_announcement" rel="noopener noreferrer"&gt;See the full walkthrough here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;First, you'll need the latest Shuttle CLI. If you don't have it installed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux/macOS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://www.shuttle.dev/install | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://www.shuttle.dev/install-win&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already have Shuttle installed, upgrade to the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring the MCP Server
&lt;/h3&gt;

&lt;p&gt;After installing the CLI, configure the MCP server in your IDE or MCP client. For Cursor, add this to your &lt;code&gt;mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Shuttle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shuttle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other IDEs and MCP clients, check the &lt;a href="https://docs.shuttle.dev/integrations/mcp-server?utm_source=shuttle_blog&amp;amp;utm_campaign=shuttle_mcp_update_announcement" rel="noopener noreferrer"&gt;configuration guide&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;

&lt;p&gt;The fastest way to try this is with a Shuttle template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect your AI agent with the MCP server, and tell it to deploy. Your app will be live in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use the Shuttle MCP Server
&lt;/h2&gt;

&lt;p&gt;Here are some practical prompts to test with your AI agent once you have the MCP server configured:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If the AI agent tries to execute the prompt directly without using the MCP tools first, make sure to prompt it explicitly to use the Shuttle MCP server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Deployment and Migration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy my app to Shuttle&lt;/li&gt;
&lt;li&gt;Migrate my Rust app to a Shuttle app&lt;/li&gt;
&lt;li&gt;Set up a Shuttle Database for me&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Debugging and Monitoring:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check my production logs and see if we have any issues&lt;/li&gt;
&lt;li&gt;How many Shuttle projects do I have?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Questions and Documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to scale my compute size on Shuttle?&lt;/li&gt;
&lt;li&gt;How to set up a Shuttle Database?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This changes how you code with Shuttle. Your AI agent becomes a proper development partner that understands your entire deployment workflow - it can answer questions about your project configuration, check deployment status, review logs when something breaks, and guide you through scaling decisions. The friction between writing code and shipping it basically disappears.&lt;/p&gt;

&lt;p&gt;Try it now and see the difference for yourself. Check out our &lt;a href="https://docs.shuttle.dev/getting-started/quick-start?utm_source=shuttle_blog&amp;amp;utm_campaign=shuttle_mcp_update_announcement" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt; to set everything up.&lt;/p&gt;

&lt;p&gt;Or get started with our most popular template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would love to see what you build with this. Join our &lt;a href="https://discord.gg/shuttle" rel="noopener noreferrer"&gt;Discord community&lt;/a&gt; and share your feedback with us.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>shuttle</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Building a Rust API with Claude Sonnet 4.5</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Wed, 01 Oct 2025 15:04:55 +0000</pubDate>
      <link>https://dev.to/shuttle/building-a-rust-api-with-claude-sonnet-45-1mfm</link>
      <guid>https://dev.to/shuttle/building-a-rust-api-with-claude-sonnet-45-1mfm</guid>
      <description>&lt;p&gt;Anthropic just dropped Sonnet 4.5 with a very &lt;strong&gt;BOLD CLAIM&lt;/strong&gt;: it's the &lt;em&gt;"best coding model in the world."&lt;/em&gt; That's a pretty confident statement in a crowded field of AI coding models.&lt;/p&gt;

&lt;p&gt;Sonnet has proven itself to be very powerful and capable of handling complex tasks, but lately the community were claiming that Claude is slowly getting worse and it's not as good as it first was. This is especially after OpenAI released their leading coding model &lt;strong&gt;Codex&lt;/strong&gt; model for their open source &lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;Codex CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To bring back their users, Anthropic is finally back with a new model &lt;strong&gt;Sonnet 4.5&lt;/strong&gt; and the Claude Code VSCode extension has also been updated, so you can use Sonnet 4.5 directly in VSCode or Cursor for a better experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuexh63x38w5cehkm5ri6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuexh63x38w5cehkm5ri6.png" alt="Claude Code VSCode Extension" width="561" height="916"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's see if Sonnet 4.5 is actually worth the hype and put it to the test, we as Rust developers like coding in Rust so this is what this blog post is about, we'll let Sonnet 4.5 build a Rust API for us that collects from 3 RSS feeds and serves them through an HTTP API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an RSS Aggregator API with Sonnet 4.5
&lt;/h2&gt;

&lt;p&gt;The goal might seem simple at first, but it's not what we're interested in, building the API is easy, what matters is how well Sonnet 4.5 can handle the task and with how many prompts and iterations we can get the API to work exactly as we want.&lt;/p&gt;

&lt;p&gt;Here is what we care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Up to date code&lt;/strong&gt;: Writing up to date code without out of date dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy&lt;/strong&gt;: How well Sonnet 4.5 can understand the task and deliver the correct code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: How fast Sonnet 4.5 compared to Sonnet 4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Capabilities&lt;/strong&gt;: How good is it in calling MCP tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Prediction&lt;/strong&gt;: How well Sonnet 4.5 can predict and handle errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbttkol5l0k9o0oe13xt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbttkol5l0k9o0oe13xt1.png" alt="Claude Code interface" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's give it the first prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a Rust RSS aggregator API that fetches these 3 feeds:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Hacker News: &lt;a href="https://news.ycombinator.com/rss" rel="noopener noreferrer"&gt;https://news.ycombinator.com/rss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Rust Blog: &lt;a href="https://blog.rust-lang.org/feed.xml" rel="noopener noreferrer"&gt;https://blog.rust-lang.org/feed.xml&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;
XKCD: &lt;a href="https://xkcd.com/rss.xml" rel="noopener noreferrer"&gt;https://xkcd.com/rss.xml&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Endpoints:&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;GET /feeds - all items as JSON&lt;/p&gt;



&lt;p&gt;GET /feeds/{source} - filtered by source&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Use Axum&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a very high level prompt, the only technical requirement is to use Axum and the endpoints should be GET /feeds and GET /feeds/{source}.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4i1nhbx66p35si66fd22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4i1nhbx66p35si66fd22.png" alt="Initial implementation complete" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First Impressions
&lt;/h2&gt;

&lt;p&gt;Sonnet 4.5 got to work immediately, created the project and wrote the code for implementing all requirements.&lt;/p&gt;

&lt;p&gt;A few things stood out right away:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Good:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It nailed the implementation. The code was straightforward, idiomatic Rust with proper async/await patterns&lt;/li&gt;
&lt;li&gt;Noticeably faster than Sonnet 4.0. The responses came back quicker, and it seemed more decisive in its approach&lt;/li&gt;
&lt;li&gt;It understood the task completely and delivered exactly what we asked for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Not-So-Good:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It used Axum 0.7 instead of the current 0.8, which has been out for almost 10 months.&lt;/li&gt;
&lt;li&gt;The RSS feeds we provided actually have different XML formats, Sonnet was supposed to normalize them into a unified JSON structure, but it didn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at the code, the code compiles and runs, the API however isn't returning the feed for the Rust Blog due to lack of normalization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterating: Normalizing the Feeds
&lt;/h2&gt;

&lt;p&gt;I gave Sonnet a follow-up prompt to normalize the feeds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The structure of each feed is different, make sure you fetch them first and then normalize them into a unified JSON structure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It performed exceptionally well, it first sent a GET request to each of the RSS feeds to fetch the data and understand their structure, then it normalized the data into a unified JSON structure, which is exactly what I wanted it to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssyvaqsbx7irno0jrrgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssyvaqsbx7irno0jrrgf.png" alt="Testing RSS feed with curl" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk9zcjc3wfakmfi65pqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk9zcjc3wfakmfi65pqn.png" alt="Feed normalization complete" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Testing the &lt;code&gt;/feeds&lt;/code&gt; endpoint, everything was working as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fez0txjer2mbdtasnhz2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fez0txjer2mbdtasnhz2w.png" alt="RSS feed output" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two prompts. That's all it took to go from initial implementation to a fully working, normalized RSS aggregator. That's really impressive, and the speed in which it did it is something I can't express enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Dependencies Fresh
&lt;/h2&gt;

&lt;p&gt;There was still one thing bugging me: the outdated Axum version. Let's give another high level prompt and ask it to update the dependencies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some of the crates you've used are outdated. Make sure they're all up to date and read their documentation to make sure there aren't any breaking changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Didn't specify which crates were outdated, let's let Sonnet figure it out itself.&lt;/p&gt;

&lt;p&gt;There's something to note here, Axum 0.8 has a breaking change in the route syntax, the old 0.7 way was &lt;code&gt;/:id&lt;/code&gt; for path parameters, but 0.8 requires &lt;code&gt;/\{id\}&lt;/code&gt; instead. If Sonnet doesn't catch this breaking change, it will cause the application to panic at runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvoi0gv10pst1gkt4emqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvoi0gv10pst1gkt4emqr.png" alt="Fetching documentation" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1o0caxfgvx6t7wbtwjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1o0caxfgvx6t7wbtwjz.png" alt="Found outdated crates" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you might be aware by now, MCPs aren't very friendly when it comes to keeping the context clean, my first thought was "Too many MCP calls is going to blow up the context window." which is true but the important thing is that the model stays sharp and doesn't lose focus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6abi0mows1bqpbov6da8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6abi0mows1bqpbov6da8.png" alt="Updated dependencies" width="619" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8v3rnjhhhasb1v08gqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8v3rnjhhhasb1v08gqr.png" alt="Axum 0.8 route update" width="737" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It actually loooked up the documentation for Axum 0.8 and updated the route syntax correctly. &lt;code&gt;cargo build&lt;/code&gt; works, and no runtime errors and both endpoints work perfectly.&lt;/p&gt;

&lt;p&gt;Three prompts total with zero compilation errors so far is quite impressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Shuttle
&lt;/h2&gt;

&lt;p&gt;With a working API in hand, there was one more test: deployment. Claude Code has MCP (Model Context Protocol) integration with Shuttle, so I wanted to see if Sonnet could handle the entire deployment workflow.&lt;/p&gt;

&lt;p&gt;I have the &lt;a href="https://docs.shuttle.dev/integrations/mcp-server" rel="noopener noreferrer"&gt;Shuttle MCP&lt;/a&gt; installed in Claude, so it can interact with the Shuttle platform. To install it, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add Shuttle shuttle mcp start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also have the &lt;a href="https://docs.shuttle.dev/getting-started/installation" rel="noopener noreferrer"&gt;Shuttle CLI installed&lt;/a&gt; and logged in to Shuttle, I recommend running &lt;code&gt;shuttle upgrade&lt;/code&gt; if you already have it installed, you can do this by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I gave it this prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want you to use the Shuttle MCP to search the docs on how to convert the app to a Shuttle app and then deploy it to Shuttle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It identified the required changes and updated the dependencies to include Shuttle runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeelm79aeuaqfpr5jbke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeelm79aeuaqfpr5jbke.png" alt="Adding Shuttle dependencies" width="592" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then converted the app to a Shuttle app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdha1arwsa4uptra2nj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdha1arwsa4uptra2nj4.png" alt="Converting to Shuttle app" width="669" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the conversion complete, it proceeded to deploy the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq1ha2u1q4ryie8vyq8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq1ha2u1q4ryie8vyq8b.png" alt="Deploying to Shuttle" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52yzm0jzc64si51fc4ue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52yzm0jzc64si51fc4ue.png" alt="Shuttle deployment logs" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feonqqp3nrew4ol4daufc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feonqqp3nrew4ol4daufc.png" alt="Deployment success" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There you go, the RSS aggregator was live and accessible. The days have changed, the entire process from prompt to production took three simple instructions and maybe five minutes of actual work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr287xz6sdgl1qf25m8xn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr287xz6sdgl1qf25m8xn.png" alt="Live API URL" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results Summary
&lt;/h2&gt;

&lt;p&gt;Here's how Sonnet 4.5 performed on our key criteria:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Performance&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Up to date code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Mixed&lt;/td&gt;
&lt;td&gt;Initially used Axum 0.7, but quickly updated to 0.8 when prompted and correctly handled breaking changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;Nailed the implementation on first try, understood complex requirements like feed normalization and no compilation errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;Noticeably faster than Sonnet 4.0, responses came back quicker and more decisively&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP Capabilities&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;Seamlessly used MCP tools to fetch RSS feeds, check documentation, and deploy to Shuttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Prediction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Good&lt;/td&gt;
&lt;td&gt;Caught Axum 0.8 breaking changes and updated route syntax correctly, but missed that RSS feeds have different structures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Overall Score: 9/10&lt;/strong&gt; - Sonnet 4.5 delivered a production-ready API in just 3 prompts with zero compilation errors and successful deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sonnet 4.5 delivered on its promise. Three prompts, zero compilation errors, and a fully deployed Rust API. The speed improvements are noticeable, and the MCP integration makes deployment seamless. While it's not perfect (it did use outdated dependencies initially), it quickly corrected course when prompted.&lt;/p&gt;

&lt;p&gt;What matters is that you always keep the context clean and write your &lt;code&gt;CLAUDE.md&lt;/code&gt; files with caution, do not overfill the context window with junk and always keep updating it and iterating on it until you get the results you want.&lt;/p&gt;

&lt;p&gt;The "best coding model" could be true. The combination of accuracy, speed, and tool integration makes it a compelling choice for Rust development.&lt;/p&gt;

&lt;p&gt;Want to try building something similar? Get started quickly with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/ai-assisted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rust</category>
      <category>ai</category>
      <category>claude</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Pandas vs Polars: Which Data Processor Runs Faster</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Thu, 25 Sep 2025 12:06:16 +0000</pubDate>
      <link>https://dev.to/shuttle/pandas-vs-polars-which-data-processor-runs-faster-40gi</link>
      <guid>https://dev.to/shuttle/pandas-vs-polars-which-data-processor-runs-faster-40gi</guid>
      <description>&lt;p&gt;When your data science workflows start hitting performance walls, the choice between Python's Pandas and Rust's Polars becomes critical. I recently discovered this firsthand while processing millions of rows of data that pushed my Python scripts to their breaking point.&lt;/p&gt;

&lt;p&gt;The problem many data engineering teams face today isn't just about handling large datasets; it's about doing it efficiently without burning through compute resources or waiting hours for ETL processes to complete. Traditional Python approaches with Pandas, while familiar and feature-rich, often become bottlenecks as data volumes grow.&lt;/p&gt;

&lt;p&gt;This article will walk you through a comprehensive performance comparison between Pandas and Polars using real-world data processing tasks. You'll see exact code implementations, actual benchmark results, and learn how to deploy high-performance data pipelines using Shuttle. The results will change how you approach data processing in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark Dataset: NYC Taxi Trip Data for Real-World ETL
&lt;/h2&gt;

&lt;p&gt;For this comparison, I used the &lt;a href="https://www.kaggle.com/datasets/elemento/nyc-yellow-taxi-trip-data" rel="noopener noreferrer"&gt;NYC Yellow Taxi dataset from January 2015—12.7 million trip records&lt;/a&gt; stored in CSV files totalling about 2.1 GB. This dataset serves as an excellent proxy for real-world ETL challenges that data science teams encounter daily.&lt;/p&gt;

&lt;p&gt;The dataset characteristics make it representative of typical production scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scale&lt;/strong&gt;: 12.7 million rows with 19 columns across multiple data types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data quality issues&lt;/strong&gt;: Missing values, invalid coordinates, and outlier detection requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed operations&lt;/strong&gt;: Requires loading data, cleaning, aggregations, and complex filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world complexity&lt;/strong&gt;: Timestamps, geospatial coordinates, and categorical data sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ETL pipeline covers five core operations that appear in most data processing workflows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load&lt;/strong&gt;: Reading CSV data from storage into memory or lazy frames&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean&lt;/strong&gt;: Handling missing data, filtering invalid values, and data type conversions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate&lt;/strong&gt;: Grouping operations across temporal and categorical dimensions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt;: Complex multi-condition filtering and sorting operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt;: Writing processed results back to storage systems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This represents typical data pipeline tasks in production environments where teams process transaction logs, sensor data, or user behaviour analytics regularly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum6owjmy2erljn72r2bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum6owjmy2erljn72r2bn.png" alt="Screenshot of Dataset sample showing column structure and data types from NYC taxi data" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Bottlenecks in Python ETL with Pandas
&lt;/h2&gt;

&lt;p&gt;Before diving into solutions, let's examine the specific performance bottlenecks that make Pandas challenging for large-scale data processing operations. These limitations become apparent when working with datasets that exceed available system memory or require complex data transformations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eager Loading and Memory Bloat
&lt;/h3&gt;

&lt;p&gt;Pandas uses eager evaluation, meaning every operation executes immediately and creates intermediate results in memory. When you load CSV files, Pandas reads the entire dataset into RAM regardless of whether you'll use all columns or rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="c1"&gt;# Pandas immediately loads entire file into memory
&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yellow_tripdata_2015-01.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.1 GB file
&lt;/span&gt;&lt;span class="n"&gt;load_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Loaded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;load_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory usage: ~4.6 GB peak&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This eager approach creates memory pressure as each transformation step generates new DataFrames, leading to memory usage that can exceed 2-3x the original dataset size during processing operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Global Interpreter Lock Problem
&lt;/h3&gt;

&lt;p&gt;Python's GIL prevents true multi-threaded execution for CPU-intensive operations, meaning Pandas can only utilize one CPU core at a time for most data processing tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This aggregation uses only one CPU core despite having 8+ cores available
&lt;/span&gt;&lt;span class="n"&gt;daily_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;std&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern systems with 8, 16, or more CPU cores remain underutilized, creating a significant performance bottleneck for data-intensive operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Missing Values and Data Types
&lt;/h3&gt;

&lt;p&gt;Pandas processes missing values through multiple passes over the data, with each operation requiring a full scan of all rows and columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Each operation scans the entire dataset separately
&lt;/span&gt;&lt;span class="n"&gt;df_cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pickup_longitude&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pickup_latitude&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df_filled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_cleaned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;df_typed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_filled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;int32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These sequential operations become increasingly expensive as datasets grow, particularly when dealing with wide schemas containing many columns with different data types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg0gvy2g3t0cj0ylc5i5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg0gvy2g3t0cj0ylc5i5.png" alt="System monitor showing single-core CPU usage during Pandas operations" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Polars Uses Rust for Fast, Multi-Threaded Data Processing
&lt;/h2&gt;

&lt;p&gt;Polars takes a fundamentally different approach to data processing by leveraging Rust's performance characteristics and implementing lazy evaluation throughout the system. This architecture enables significant performance improvements for ETL operations on large datasets. To understand why polars works so well, let's look at the key features of polars:&lt;/p&gt;

&lt;h3&gt;
  
  
  Lazy Evaluation and Query Planning
&lt;/h3&gt;

&lt;p&gt;Instead of executing operations immediately, Polars builds a query plan that gets optimized before any actual data processing begins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;polars&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create lazy frame - no data loading yet&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yellow_tripdata_2015-01.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="nf"&gt;.group_by&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.dt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.date&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="nf"&gt;.agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.mean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

&lt;span class="c1"&gt;// Only execute when explicitly requested&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lazy approach allows Polars to analyze the entire pipeline and apply optimizations like predicate pushdown, column pruning, and operation fusion before touching any data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Optimization Techniques
&lt;/h3&gt;

&lt;p&gt;Polars automatically applies several query optimization techniques that reduce I/O operations and memory usage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Predicate Pushdown&lt;/strong&gt;: Filters get moved closer to data sources, reducing the amount of data that needs to be loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Polars pushes this filter down to the CSV reader level&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;filtered_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;// Applied during file reading&lt;/span&gt;
    &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Column Pruning&lt;/strong&gt;: Only required columns get loaded from storage, reducing memory usage and I/O:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only loads pickup_datetime and trip_distance columns&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Core Processing and Memory Efficiency
&lt;/h3&gt;

&lt;p&gt;Rust's native threading capabilities allow Polars to utilize all available CPU cores automatically. Operations like aggregations, joins, and sorting distribute work across threads without the GIL limitations that constrain Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Automatically uses all CPU cores for groupby operations&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;daily_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.group_by&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.dt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.date&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="nf"&gt;.agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_count"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"avg_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_revenue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Parallel execution across all cores&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Memory efficiency comes from Rust's ownership system and Polars' streaming capabilities, which process data in chunks rather than loading entire datasets into memory. As the graph belows shows, Polars also maximizes CPU utilization, distributing work across all cores for consistently fast execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuc66sihbjqchwfnbyzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuc66sihbjqchwfnbyzd.png" alt="CPU usage graph showing all cores utilized during Polars operations" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1ro46gbqeqjlugf6fps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1ro46gbqeqjlugf6fps.png" alt="CPU usage graph showing all cores utilized during Polars operations" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pandas vs Polars ETL Pipeline Examples
&lt;/h2&gt;

&lt;p&gt;Let's examine side-by-side implementations of the same ETL pipeline using both libraries. These examples show identical data processing logic implemented with each tool's best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pandas Implementation: Traditional ETL Approach
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PandasETL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_and_clean_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Load CSV data and perform cleaning operations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Loading and cleaning data...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Load entire CSV into memory
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Clean invalid coordinates and trip data
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pickup_longitude&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pickup_latitude&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Convert datetime columns
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_dropoff_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_dropoff_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Calculate trip duration
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_duration_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_dropoff_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;

        &lt;span class="c1"&gt;# Remove trips with invalid duration
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_duration_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_duration_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;load_clean_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;load_clean_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_clean_time&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Loaded and cleaned &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;load_clean_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;aggregate_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Perform aggregation operations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Performing aggregations...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Add date columns for grouping
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hour&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tpep_pickup_datetime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;

        &lt;span class="c1"&gt;# Daily statistics
&lt;/span&gt;        &lt;span class="n"&gt;daily_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_duration_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passenger_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;# Hourly patterns
&lt;/span&gt;        &lt;span class="n"&gt;hourly_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hour&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trip_distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="n"&gt;aggregate_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aggregate_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aggregate_time&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Aggregations completed in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aggregate_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Polars Implementation: Lazy ETL Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;polars&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PolarsETL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PolarsETL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_etl_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PolarsResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"🚀 Starting Polars ETL pipeline..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Build lazy query plan&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lazy_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_longitude"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_latitude"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_dropoff_datetime"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="c1"&gt;// Apply filters (pushed down to scan level)&lt;/span&gt;
            &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_longitude"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.neq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_latitude"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.neq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.lt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.lt_eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Parse datetime columns&lt;/span&gt;
            &lt;span class="nf"&gt;.with_columns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nn"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Microseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nn"&gt;StrptimeOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"coerce"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_dropoff_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nn"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Microseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nn"&gt;StrptimeOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"coerce"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="c1"&gt;// Calculate trip duration&lt;/span&gt;
            &lt;span class="nf"&gt;.with_columns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_dropoff_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="nf"&gt;.dt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.total_minutes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_duration_minutes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="c1"&gt;// Filter by trip duration&lt;/span&gt;
            &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_duration_minutes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_duration_minutes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.lt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;480.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Execute aggregations&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;daily_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lazy_df&lt;/span&gt;
            &lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.with_columns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tpep_pickup_datetime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.dt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
            &lt;span class="nf"&gt;.group_by&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
            &lt;span class="nf"&gt;.agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_count"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"avg_trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_duration_minutes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"avg_duration"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_passengers"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"avg_fare"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_revenue"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_secs_f64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_time"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ ETL pipeline completed in {:.2f}s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Setup for Reproducible Results
&lt;/h3&gt;

&lt;p&gt;To run these benchmarks consistently, I used the following setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Python environment&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv pandas_env
&lt;span class="nb"&gt;source &lt;/span&gt;pandas_env/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;2.1.0 &lt;span class="nv"&gt;numpy&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.24.0 psutil

&lt;span class="c"&gt;# Rust environment&lt;/span&gt;
curl &lt;span class="nt"&gt;--proto&lt;/span&gt; &lt;span class="s1"&gt;'=https'&lt;/span&gt; &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.2 &lt;span class="nt"&gt;-sSf&lt;/span&gt; https://sh.rustup.rs | sh
cargo &lt;span class="nt"&gt;--version&lt;/span&gt;
rustc &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# System specifications for reproducibility&lt;/span&gt;
&lt;span class="c"&gt;# CPU: 8-core Intel i7 (16 threads)&lt;/span&gt;
&lt;span class="c"&gt;# RAM: 32GB DDR4&lt;/span&gt;
&lt;span class="c"&gt;# Storage: NVMe SSD&lt;/span&gt;
&lt;span class="c"&gt;# OS: Ubuntu 22.04 LTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrsrjb19g6aw2vvy71df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrsrjb19g6aw2vvy71df.png" alt="Code Comparison Showing Pandas vs Polars side by side" width="694" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b4jh43btdbk4ozwhskb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b4jh43btdbk4ozwhskb.png" alt="Code comparison showing Pandas vs Polars side by side" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These side-by-side implementations highlight the key design differences between Pandas and Polars. Pandas follows an eager, memory-intensive approach, while Polars builds an optimized lazy query plan that executes more efficiently. With both pipelines producing the same analytical outputs, the real distinction emerges in how they perform at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polars vs Pandas Performance on ETL Tasks
&lt;/h2&gt;

&lt;p&gt;After running identical ETL operations on the 12.7 million row NYC taxi dataset, the performance differences are substantial. Here are the detailed benchmark results across all major operations:&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution Time Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ETL Operation&lt;/th&gt;
&lt;th&gt;Pandas (seconds)&lt;/th&gt;
&lt;th&gt;Polars (seconds)&lt;/th&gt;
&lt;th&gt;Speedup Factor&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load + Clean&lt;/td&gt;
&lt;td&gt;43.60&lt;/td&gt;
&lt;td&gt;Deferred&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Polars defers load/clean until execution phase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregations&lt;/td&gt;
&lt;td&gt;9.21&lt;/td&gt;
&lt;td&gt;13.80&lt;/td&gt;
&lt;td&gt;0.67x *&lt;/td&gt;
&lt;td&gt;Polars executes load + clean + aggregation together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter + Sort&lt;/td&gt;
&lt;td&gt;9.42&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;1.8x&lt;/td&gt;
&lt;td&gt;Polars benefits from predicate pushdown &amp;amp; parallelism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export Results&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;2.8x&lt;/td&gt;
&lt;td&gt;Polars writes faster due to streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Pipeline&lt;/td&gt;
&lt;td&gt;62.37s&lt;/td&gt;
&lt;td&gt;19.10s&lt;/td&gt;
&lt;td&gt;3.3x&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Includes deferred load/clean phase in Polars&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory Usage Analysis
&lt;/h3&gt;

&lt;p&gt;These results are environment-specific. In practice, Polars often uses 30-60% less memory on large CSV workloads due to column pruning and streaming, though actual savings depend on schema and operations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pandas Memory Profile
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Peak usage: 4,658 MB during processing operations&lt;/li&gt;
&lt;li&gt;Memory pattern: Immediate spike during CSV loading&lt;/li&gt;
&lt;li&gt;Garbage collection: Frequent pauses for cleanup&lt;/li&gt;
&lt;li&gt;Intermediate objects: Multiple DataFrame copies in memory&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Polars Memory Profile
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Peak usage: ~2,100 MB during aggregation execution&lt;/li&gt;
&lt;li&gt;Memory pattern: Steady increase only during actual processing&lt;/li&gt;
&lt;li&gt;No garbage collection: Rust's ownership system manages memory&lt;/li&gt;
&lt;li&gt;Streaming operations: Data processed in manageable chunks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CPU Utilization Patterns
&lt;/h3&gt;

&lt;p&gt;Polars automatically parallelizes across available cores. Pandas relies on single-threaded execution for most operations unless explicitly offloaded (e.g., via Dask, Modin).&lt;/p&gt;

&lt;h4&gt;
  
  
  Pandas CPU Usage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Single-thread utilization: ~12.5% of 8-core system (1 core)&lt;/li&gt;
&lt;li&gt;GIL limitations: Other threads blocked during computation&lt;/li&gt;
&lt;li&gt;Load balancing: Uneven system resource usage&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Polars CPU Usage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Multi-thread utilization: ~85% of 8-core system (all cores)&lt;/li&gt;
&lt;li&gt;Parallel operations: Concurrent processing across cores&lt;/li&gt;
&lt;li&gt;Efficient scheduling: Even load distribution across threads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Polars is Faster
&lt;/h2&gt;

&lt;p&gt;The dramatic performance differences stem from fundamental architectural choices. Understanding these differences helps explain when and why you might choose one approach over the other for your data processing systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eager vs Lazy Execution Models
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pandas Eager Execution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Each operation executes immediately
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# Load: 32.5s
&lt;/span&gt;&lt;span class="n"&gt;df_clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;distance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# Filter: 8.2s
&lt;/span&gt;&lt;span class="n"&gt;df_agg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df_clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Aggregate: 9.1s
# Total: 49.8s across separate operations
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Polars Lazy Execution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Build query plan without execution&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;// Added to plan: ~0s&lt;/span&gt;
    &lt;span class="nf"&gt;.group_by&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;           &lt;span class="c1"&gt;// Added to plan: ~0s&lt;/span&gt;
    &lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                             &lt;span class="c1"&gt;// Added to plan: ~0s&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                       &lt;span class="c1"&gt;// Execute all: 13.8s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lazy approach allows Polars to optimize the entire pipeline as a single operation, eliminating intermediate steps and reducing data movement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Efficient Memory Access Patterns
&lt;/h3&gt;

&lt;p&gt;Polars leverages several memory optimization techniques:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Columnar Data Layout&lt;/strong&gt;: Data stored column-wise enables better cache locality and vectorized operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SIMD Instructions&lt;/strong&gt;: Single Instruction, Multiple Data processing accelerates numerical computations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-Copy Operations&lt;/strong&gt;: Data transformations avoid unnecessary memory allocation when possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming Execution&lt;/strong&gt;: Large datasets are processed in chunks that fit in the CPU cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Rewriting and Optimization
&lt;/h3&gt;

&lt;p&gt;Polars automatically rewrites queries for better performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Original query&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;                    &lt;span class="c1"&gt;// Select all columns&lt;/span&gt;
    &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;         &lt;span class="c1"&gt;// Filter expensive trips&lt;/span&gt;
    &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;  &lt;span class="c1"&gt;// Select subset&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Polars optimization rewrites this to:&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Scan only date and amount columns (column pruning)&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Apply filter during CSV reading (predicate pushdown)&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Skip unnecessary intermediate selections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These optimizations occur automatically, without requiring code changes, making Polars faster while maintaining simplicity. &lt;strong&gt;But performance alone isn't the full story.&lt;/strong&gt; Once you've squeezed every ounce of speed from your ETL pipeline, the next challenge emerges: &lt;em&gt;how do you take that optimized workflow and actually run it in production at scale, reliably, and without DevOps headaches?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Benchmarking on your laptop is one thing; managing deployments, scaling, SSL certificates, and infrastructure is another. &lt;strong&gt;That's where Shuttle comes in.&lt;/strong&gt; With Shuttle, you can deploy your Polars ETL pipeline as a production-ready API in just a few commands, no containers, no load balancers, no endless YAML files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying a Rust ETL Pipeline with Shuttle
&lt;/h2&gt;

&lt;p&gt;Of course, benchmarks are only half the story. The real challenge is turning a fast local pipeline into something production-ready. That's where Shuttle helps: it lets you deploy Polars pipelines as APIs without wrestling with infra. Traditional Rust deployment can be complex, but Shuttle abstracts away the infrastructure management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Production ETL API
&lt;/h3&gt;

&lt;p&gt;Here's how to wrap our Polars ETL pipeline in a web API suitable for production use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tower_http&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CorsLayer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ETLResults&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;processing_time_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rows_processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;daily_statistics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DailyStats&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;performance_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DailyStats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;trip_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;avg_distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run_etl_benchmark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ETLResults&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;etl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PolarsETL&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// In production, you'd load from your data warehouse&lt;/span&gt;
    &lt;span class="c1"&gt;// For demo purposes, we return representative results&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ETLResults&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;processing_time_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;19.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rows_processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12_748_986&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;daily_statistics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;create_sample_stats&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;performance_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Processed 12.7M taxi records in 19.1 seconds using Polars"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"healthy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Polars ETL Pipeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"high_throughput_processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"multi_core_execution"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"memory_efficient"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/etl/benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_etl_benchmark&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CorsLayer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;permissive&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Simple Shuttle Deployment Process
&lt;/h3&gt;

&lt;p&gt;Deploying this ETL pipeline to production requires minimal configuration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Deploy with three commands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Shuttle CLI&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;cargo-shuttle

&lt;span class="c"&gt;# Login to Shuttle&lt;/span&gt;
shuttle login

&lt;span class="c"&gt;# Deploy to production&lt;/span&gt;
shuttle deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd57vu4jhty1iv4qnvhp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd57vu4jhty1iv4qnvhp7.png" alt="Screenshot showing cargo shuttle deployed" width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9abjvzws4rx473zwt00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9abjvzws4rx473zwt00.png" alt="Screenshot showing cargo shuttle deployed" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shuttle handles all the complex infrastructure concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Container orchestration and scaling&lt;/li&gt;
&lt;li&gt;Load balancing and networking&lt;/li&gt;
&lt;li&gt;SSL certificate management&lt;/li&gt;
&lt;li&gt;Monitoring and logging systems&lt;/li&gt;
&lt;li&gt;Automatic deployments from Git&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration with Data Systems
&lt;/h3&gt;

&lt;p&gt;For production use, you can connect this pipeline to various data sources and destinations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: Reading from cloud storage&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s3://data-bucket/taxi-data/*.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Writing to data warehouse&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.write_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s3://output-bucket/processed-data.parquet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ParquetWriteOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Example: Streaming processing&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;streaming_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyFrame&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data/*.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ScanArgsCSV&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.with_streaming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Process in chunks&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migrating from Pandas to Polars: A Practical Guide
&lt;/h2&gt;

&lt;p&gt;The decision to migrate from Pandas to Polars shouldn't be all-or-nothing. Here's a practical approach for teams considering the transition while minimizing risk and disruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Switch and When to Stick with Pandas
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Consider Polars when&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing datasets larger than available RAM&lt;/li&gt;
&lt;li&gt;ETL operations take more than a few minutes to complete&lt;/li&gt;
&lt;li&gt;Memory usage becomes a limiting factor in your systems&lt;/li&gt;
&lt;li&gt;CPU cores remain underutilized during data processing&lt;/li&gt;
&lt;li&gt;You need predictable performance characteristics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stick with Pandas when&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Working with datasets under 1GB consistently&lt;/li&gt;
&lt;li&gt;Heavy use of domain-specific libraries that integrate with Pandas&lt;/li&gt;
&lt;li&gt;Rapid prototyping, where development speed matters more than execution speed&lt;/li&gt;
&lt;li&gt;Team lacks Rust experience, and the timeline is tight&lt;/li&gt;
&lt;li&gt;Complex data science workflows with many specialized functions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hybrid Workflows: Wrapping Heavy Steps in Polars
&lt;/h3&gt;

&lt;p&gt;You don't need to rewrite entire systems. Start by identifying performance bottlenecks and replacing them with Polars operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;polars&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hybrid_etl_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Use Polars for heavy data loading and cleaning
&lt;/span&gt;    &lt;span class="n"&gt;polars_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_columns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert to Pandas for specialized analysis
&lt;/span&gt;    &lt;span class="n"&gt;pandas_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;polars_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_pandas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Use existing Pandas-based analysis code
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;perform_statistical_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pandas_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert back to Polars for final aggregation
&lt;/span&gt;    &lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pandas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;final_result&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform_statistical_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Existing Pandas code remains unchanged
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;complex_statistical_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Polars Without Rewriting Your Pipeline
&lt;/h3&gt;

&lt;p&gt;Start with a proof-of-concept approach that validates performance improvements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Benchmark existing Pandas operations
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark_pandas_operation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large_dataset.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;pandas_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pandas_time&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Implement equivalent Polars version
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;benchmark_polars_operation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large_dataset.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;\
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;polars_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polars_time&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Compare results and performance
&lt;/span&gt;&lt;span class="n"&gt;pandas_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pandas_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;benchmark_pandas_operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;polars_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polars_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;benchmark_polars_operation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pandas: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pandas_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Polars: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;polars_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Speedup: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pandas_time&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;polars_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gradual Migration Strategy
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 1: Identify bottlenecks&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Profile existing code to find slowest operations&lt;/li&gt;
&lt;li&gt;Measure current memory usage and processing time&lt;/li&gt;
&lt;li&gt;Document data types and transformations used&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 2: Proof of concept&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Implement one critical operation in Polars&lt;/li&gt;
&lt;li&gt;Validate identical results between implementations&lt;/li&gt;
&lt;li&gt;Measure performance improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 3: Expand coverage&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Replace additional heavy operations&lt;/li&gt;
&lt;li&gt;Build team familiarity with Polars syntax&lt;/li&gt;
&lt;li&gt;Update deployment processes to handle Rust code&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Phase 4: Full migration&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Convert remaining operations where beneficial&lt;/li&gt;
&lt;li&gt;Optimize query patterns for maximum performance&lt;/li&gt;
&lt;li&gt;Update monitoring and alerting systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Takeaways
&lt;/h2&gt;

&lt;p&gt;Our benchmarks showed Polars delivering a &lt;strong&gt;3.3x&lt;/strong&gt; speedup over Pandas for this ETL workload, with significantly lower memory usage and full CPU utilization. This performance comes from its modern architecture: &lt;strong&gt;lazy evaluation&lt;/strong&gt;, &lt;strong&gt;query optimization&lt;/strong&gt;, and native &lt;strong&gt;multi-threading&lt;/strong&gt; powered by Rust.&lt;/p&gt;

&lt;p&gt;However, performance isn't everything. Pandas remains the pragmatic choice for smaller datasets (&amp;lt;1 GB), rapid prototyping, and tasks deeply integrated with the broader Python data science ecosystem. In practice, many teams adopt a hybrid strategy: using Polars for heavy data preparation and falling back to Pandas for specialized analysis and ML model integration.&lt;/p&gt;

&lt;p&gt;Ultimately, if your current pipelines are hitting performance walls, Polars offers a clear path to faster, more scalable processing. But turning that local speed into a production-ready system presents the next hurdle. This is where Shuttle completes the picture. It abstracts away the complexity of containers and infrastructure, allowing you to deploy a high-performance Polars pipeline as a scalable API in minutes, not days. It turns benchmarks into real-world applications without the DevOps overhead.&lt;/p&gt;

&lt;p&gt;Ready to see the difference yourself? Deploy the complete Polars ETL benchmark and run your own comparisons with real data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AdepojuJeremy/pandas-vs-polars-benchmark" rel="noopener noreferrer"&gt;View the complete benchmark code on GitHub →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>datapipelines</category>
      <category>pandas</category>
      <category>polars</category>
    </item>
    <item>
      <title>How to Monitor Data Pipelines in Rust Using OpenTelemetry and Shuttle</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Wed, 24 Sep 2025 12:24:29 +0000</pubDate>
      <link>https://dev.to/shuttle/how-to-monitor-data-pipelines-in-rust-using-opentelemetry-and-shuttle-53en</link>
      <guid>https://dev.to/shuttle/how-to-monitor-data-pipelines-in-rust-using-opentelemetry-and-shuttle-53en</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;em&gt;Data pipelines often fail silently, corrupting data before anyone notices. We show you how to build a high-performance, observable ETL pipeline in Rust using Polars for speed, OpenTelemetry for detailed metrics and traces, and Shuttle for zero-config deployment and telemetry export. This guide provides commented code, deployment steps, and dashboard examples to help you catch failures early, pinpoint bottlenecks, and ensure data quality from start to finish.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ETL and data engineering workflows break silently in production. A CSV file processes millions of records, but somewhere in the pipeline, wrong values slip through data validation, memory usage spikes during aggregation stages, or processing slows to a crawl without clear indicators of where the bottleneck occurs. By the time data engineers notice the issue, corrupted data has already propagated downstream.&lt;/p&gt;

&lt;p&gt;Python developers working with Pandas frequently encounter this challenge. Memory blowups happen during large dataset processing, errors cascade across transformation stages with minimal context, and debugging performance issues requires manually adding print statements throughout the code. Traditional application monitoring tools focus on web request metrics such as latency, throughput, and error rates, but data pipelines need visibility into processing stages, memory consumption patterns, and data quality indicators.&lt;/p&gt;

&lt;p&gt;Here's the alternative: Rust applications with Polars deliver predictable performance, OpenTelemetry provides standardized observability, and Shuttle eliminates infrastructure complexity. By implementing such a combination, you get clean, fast, observable data pipelines that surface issues before they corrupt your data.&lt;/p&gt;

&lt;p&gt;This article explains building a production-ready data pipeline that processes real-world datasets while maintaining complete visibility into every processing stage, memory usage pattern, and performance characteristic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark Pipeline: CSV Processing with Polars
&lt;/h2&gt;

&lt;p&gt;Let me show you a working ETL example that processes &lt;a href="https://www.kaggle.com/datasets/elemento/nyc-yellow-taxi-trip-data" rel="noopener noreferrer"&gt;NYC taxi CSV files&lt;/a&gt;. This pipeline demonstrates the observability challenges that make data engineering workflows different from typical web applications.&lt;/p&gt;

&lt;p&gt;The data pipeline follows these stages: load massive CSV files (1.9GB with 12.7 million records), parse datetime fields and geographic coordinates, filter invalid records and outliers, aggregate trip patterns by time periods, and write results for downstream analysis.&lt;/p&gt;

&lt;p&gt;Each stage presents monitoring challenges. Loading CSV files requires tracking memory allocation patterns as data enters the system. Parsing operations can fail silently when date formats don't match expectations. Filtering stages need visibility into how many records get rejected and why. Aggregation operations consume the most memory and require peak usage tracking. Output operations need validation that results match expected patterns.&lt;/p&gt;

&lt;p&gt;Polars provides the performance foundation for this pipeline. Its lazy evaluation approach minimizes memory pressure, columnar data processing delivers consistent performance across large datasets, and structured operations make precise measurement possible. Unlike Pandas operations that can consume unpredictable amounts of memory, Polars operations have measurable resource requirements that can be monitored and alerted on.&lt;/p&gt;

&lt;p&gt;The NYC taxi dataset contains all the characteristics that make observability critical: high record volume, data quality issues with missing coordinates and invalid timestamps, complex geographic and temporal transformations, and memory-intensive aggregation operations. Processing this dataset demonstrates how proper instrumentation reveals bottlenecks and data quality issues before they impact production systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkmmo8nxz0ikpw0pg7g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkmmo8nxz0ikpw0pg7g8.png" alt="Screenshot of dataset sample showing column structure and data types of NYC Taxi csv files" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Monitor in Data Pipelines (and Why)
&lt;/h2&gt;

&lt;p&gt;Data pipeline monitoring differs fundamentally from web application observability. Instead of request latency and error rates, you need visibility into processing characteristics that determine pipeline reliability and data quality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stage-level execution time&lt;/strong&gt; reveals processing bottlenecks. While web applications measure individual request duration, data pipelines need timing for load operations, parsing stages, transformation steps, aggregation phases, and output generation. A single slow stage can bottleneck the entire pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Throughput and record counts&lt;/strong&gt; provide data volume insights. Monitor records processed per second, total record counts per stage, and record rejection rates during validation. These metrics help identify processing capacity limits and data quality trends.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Peak memory consumption per stage&lt;/strong&gt; prevents out-of-memory failures. Filtering operations might spike memory usage when loading large intermediate results. Aggregation stages require materializing grouped data. Memory patterns vary dramatically based on dataset characteristics and processing logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error paths and panics in Rust code&lt;/strong&gt; need special attention. Unlike garbage-collected languages, Rust memory safety prevents many runtime failures, but data pipeline errors often relate to schema mismatches, missing files, or invalid data formats. Capturing error context enables rapid debugging.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics differ from typical web application monitoring because data pipelines process discrete batches rather than continuous streams, consume resources in predictable patterns based on dataset size, and fail differently than request-response applications.&lt;/p&gt;

&lt;p&gt;Traditional application observability focuses on user-facing performance and system health. Data pipeline observability focuses on data quality, processing efficiency, and resource utilization patterns that affect downstream systems and analysis accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Observability to Rust ETL Pipeline with OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;Instrumenting Rust applications for data pipeline monitoring requires strategic placement of tracing spans and metrics collection. The observability approach needs to capture processing stage performance, memory usage patterns, and data quality indicators without significantly impacting pipeline throughput.&lt;/p&gt;

&lt;p&gt;Here's how to structure observability in your Rust data pipeline using OpenTelemetry and tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;polars&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Memory monitoring function for RSS tracking&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;rss_mb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/proc/self/status"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.lines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VmRSS:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kb&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Track peak memory across processing stages&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;bump_peak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rss_mb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}_memory_mb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"peak_memory_mb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.cloned&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"peak_memory_mb"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PolarsETL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Custom metrics storage for pipeline-specific data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PolarsETL&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Automatic span creation with contextual attributes (file path)&lt;/span&gt;
    &lt;span class="nd"&gt;#[instrument(level&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"info"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;skip(self))]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PolarsResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Manual span creation for nested tracing hierarchy&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;load_span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;span!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"etl_load"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_enter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_span&lt;/span&gt;&lt;span class="nf"&gt;.enter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Structured logging with contextual information&lt;/span&gt;
        &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Loading CSV file: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Performance timing measurement&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LazyCsvReader&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.with_has_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.with_infer_schema_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_longitude"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_latitude"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_amount"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_secs_f64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Duration calculation&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"load_time"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Custom metric storage&lt;/span&gt;
        &lt;span class="nf"&gt;bump_peak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"after_load"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Memory usage tracking&lt;/span&gt;

        &lt;span class="c1"&gt;// Structured logging with key-value metrics for observability platforms&lt;/span&gt;
        &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.load.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// Performance metric&lt;/span&gt;
            &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.load.memory_mb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"after_load_memory_mb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Resource usage&lt;/span&gt;
            &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"load"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                           &lt;span class="c1"&gt;// Pipeline stage identifier&lt;/span&gt;
            &lt;span class="s"&gt;"CSV load completed"&lt;/span&gt;                      &lt;span class="c1"&gt;// Human-readable message&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Automatic instrumentation with span naming and skipping of complex parameters&lt;/span&gt;
    &lt;span class="nd"&gt;#[instrument(level&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"info"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;skip(self))]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;clean_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PolarsResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Nested span for detailed tracing hierarchy&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clean_span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;span!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"etl_clean"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_enter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clean_span&lt;/span&gt;&lt;span class="nf"&gt;.enter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Stage-specific logging with contextual information&lt;/span&gt;
        &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting data cleaning stage"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Performance measurement start&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.df&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;
                &lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_longitude"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.neq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                        &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pickup_latitude"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.neq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                        &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                        &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"passenger_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.gt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.cache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_secs_f64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Performance timing&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clean_time"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Stage-specific metrics&lt;/span&gt;
        &lt;span class="nf"&gt;bump_peak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"after_clean"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Memory tracking per stage&lt;/span&gt;

        &lt;span class="c1"&gt;// Multi-dimensional metrics emission with contextual attributes&lt;/span&gt;
        &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.clean.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;// Stage performance&lt;/span&gt;
            &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.clean.memory_mb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.metrics&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"after_clean_memory_mb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Resource consumption&lt;/span&gt;
            &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"clean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="c1"&gt;// Pipeline stage tag&lt;/span&gt;
            &lt;span class="s"&gt;"Data cleaning completed"&lt;/span&gt;                 &lt;span class="c1"&gt;// Completion status&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spans with contextual attributes&lt;/strong&gt; provide distributed tracing capability. Each processing stage gets its own span with resource attributes like stage, file, and processing metadata. This enables tracing the whole process across complex pipeline operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instrumented functions&lt;/strong&gt; using the &lt;code&gt;#[instrument]&lt;/code&gt; attribute automatically capture function entry/exit, execution time, and error conditions. The tracing framework handles span lifecycle management and context propagation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metrics emission&lt;/strong&gt; follows OpenTelemetry conventions with structured field names like &lt;code&gt;etl.load.duration&lt;/code&gt; and &lt;code&gt;etl.clean.memory_mb&lt;/code&gt;. These metrics integrate with observability platforms for dashboard creation and alerting.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory monitoring through &lt;code&gt;rss_mb()&lt;/code&gt; reads actual RSS memory consumption from &lt;code&gt;/proc/self/status&lt;/code&gt;, providing accurate memory usage data during each processing stage. This approach works reliably in containerized environments and cloud deployments.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Platform-Specific Code&lt;/strong&gt;: This memory monitoring function reads from the &lt;code&gt;/proc&lt;/code&gt; filesystem, which is specific to Linux. For cross-platform compatibility, consider using a crate like &lt;code&gt;sysinfo&lt;/code&gt; that provides unified system information APIs across Windows, macOS, and Linux.&lt;/p&gt;

&lt;p&gt;The image below shows the memory usage monitoring for Polars, demonstrating increased memory consumption during processing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" alt="Memory usage monitoring for Polars ETL execution" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" alt="Memory usage monitoring for Polars ETL execution" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx2dm8f9ocl047mhmycu.png" alt="Memory usage monitoring for Polars ETL execution" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These images show the system monitoring during ETL pipeline execution, where the first image displays baseline performance with low CPU utilization (1-8%) and stable 5.1GB memory usage, the next image shows increased processing activity with CPU8 reaching 32.4% while memory remains stable, and the last one captures peak ETL operations with significantly higher CPU utilization (CPU8 at 85.9%) and elevated memory consumption at 9.9GB, effectively demonstrating how the Rust pipeline scales resource usage during different processing phases and validating the importance of memory monitoring.&lt;/p&gt;

&lt;p&gt;By establishing these observability goals upfront, tracking performance, memory usage, and data quality, you create a foundation for reliable monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting Observability Data to BetterStack via Shuttle
&lt;/h2&gt;

&lt;p&gt;Shuttle provides out-of-the-box OpenTelemetry integration that eliminates traditional observability infrastructure complexity. Instead of configuring collectors, managing exporters, or writing YAML configurations, you get automatic telemetry data export to your chosen observability platform.&lt;/p&gt;

&lt;p&gt;To enable this integration, include &lt;a href="https://www.shuttle.dev/blog/2025/02/19/using-shuttle-with-betterstack" rel="noopener noreferrer"&gt;Shuttle's setup-otel-exporter feature&lt;/a&gt; in your runtime configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;shuttle-runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.56"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"setup-otel-exporter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;shuttle-axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.56"&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;opentelemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure observability in your application code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SubscriberExt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;util&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SubscriberInitExt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EnvFilter&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;init_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;EnvFilter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;try_from_default_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;EnvFilter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fmt_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.with_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;UtcTime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;rfc_3339&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt_layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shuttle's approach eliminates the infrastructure complexity typically associated with observability. No Docker containers running collectors, no YAML configuration files for exporters, no manual OTLP endpoint management. The platform handles OpenTelemetry protocol export, authentication, and routing automatically.&lt;/p&gt;

&lt;p&gt;When you enable BetterStack integration in your Shuttle project settings, telemetry data flows directly from your Rust application to BetterStack's ingestion endpoints. The integration includes automatic retry logic, batching for performance, and error handling that would normally require manual configuration.&lt;/p&gt;

&lt;p&gt;This approach works because Shuttle's runtime includes a tracer provider that automatically exports spans and metrics when observability integrations are enabled. Your application code focuses on emitting telemetry data using standard OpenTelemetry patterns, while the platform handles infrastructure concerns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2xt5ve8xcq4zt1zhs8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2xt5ve8xcq4zt1zhs8u.png" alt="Open Telemetry Better Stack Portal" width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp2q82lpe8p9n9zupjnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp2q82lpe8p9n9zupjnt.png" alt="OpenTelemetry BetterStack Portal with Logs and Traces" width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The images above demonstrate the BetterStack observability platform receiving telemetry data from the Rust ETL pipeline, where the first screen shows basic log streams with application startup events including tracing subscriber initialization and service startup messages, while the second reveals detailed OpenTelemetry span data displaying trace IDs, span IDs, and stage timing information, showcasing how the integration captures both high-level application logs and granular distributed tracing data for comprehensive pipeline monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the Observable Pipeline with Shuttle
&lt;/h2&gt;

&lt;p&gt;Shuttle deployment transforms your observable Rust application into a production service with a single command. The platform's infrastructure-from-code approach means your deployment configuration lives in your application rather than external configuration files. The deployment process is broken down into steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Set up your application for Shuttle deployment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;observability&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tower_http&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CorsLayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TraceLayer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;shuttle_main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;observability&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"polars-etl-pipeline"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;api_router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CorsLayer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;permissive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TraceLayer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_for_http&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"startup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"polars-etl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Application ready"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Serialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;BenchmarkResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;throughput_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.health.check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Health check performed"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="s"&gt;"OK"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.benchmark.start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s"&gt;"Benchmark endpoint called"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Representative pipeline metrics (avoiding heavy processing on production dyno)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"load_duration_ms"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;1200.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clean_duration_ms"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"aggregate_duration_ms"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;400.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total_duration_ms"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;2800.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"records_processed"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mf"&gt;12_748_986.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Emit metrics for BetterStack dashboards&lt;/span&gt;
    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.load.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.clean.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.aggregate.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.rows.processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12_748_986_i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"complete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Pipeline benchmark completed"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="py"&gt;.request.duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Request completed"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;throughput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12_748_986.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BenchmarkResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Benchmark completed successfully"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;throughput_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Processed 12.7M records in 2.8s → {:.0} rows/sec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throughput&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;api_router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/benchmark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Deploy your observable pipeline:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy to Shuttle platform&lt;/span&gt;
shuttle deploy

&lt;span class="c"&gt;# Monitor deployment and access logs&lt;/span&gt;
shuttle status
shuttle logs &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Testing endpoints confirms telemetry data flows correctly:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test health endpoint&lt;/span&gt;
curl https://your-app.shuttle.app/health

&lt;span class="c"&gt;# Generate benchmark telemetry&lt;/span&gt;
curl https://your-app.shuttle.app/benchmark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shuttle automatically streams metrics to BetterStack once you configure the integration in your project settings. The platform handles authentication, retry logic, and batching that would normally require manual OTLP configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhwkcem0ve77vnsz4vx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhwkcem0ve77vnsz4vx0.png" alt="Observable Pipeline Deployed on Shuttle" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0nwt5hr6wonl5id8lgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0nwt5hr6wonl5id8lgz.png" alt="Observable Pipeline CLI Benchmarks" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" alt="Observable Pipeline Showing CLI Telemetry information" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" alt="Observable Pipeline Showing CLI Telemetry information" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" alt="Observable Pipeline Showing CLI Telemetry information" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhq259avu4flzpozucfvr.png" alt="Observable Pipeline Showing CLI Telemetry Information" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feiyzvlhau8vct8n4a10r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feiyzvlhau8vct8n4a10r.png" alt="Shuttle Showing the Deployed Application Running" width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above images demonstrate the complete development-to-production workflow for the observable Rust ETL pipeline. The initial screens show local CLI execution with structured JSON logging, revealing detailed performance metrics including 108-second total runtime and stage-by-stage timing breakdown, while subsequent displays present comprehensive telemetry output with dataset information, processing statistics of 12.7M records processed at 4.5M records/second, and deployment configuration details. The workflow continues with the deployed Shuttle application's API endpoints for health checks and benchmarking, followed by captures of the Shuttle deployment process and dashboard showing the running application with telemetry integration and log streaming capabilities, further showing how the same codebase provides both detailed local benchmarking and production-ready observability through feature flags and deployment automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Dashboards for ETL Monitoring in BetterStack
&lt;/h2&gt;

&lt;p&gt;When running ETL pipelines, it's not enough to just know they work; you need visibility into how they perform over time. BetterStack dashboards provide that visibility at a glance by transforming your telemetry data into actionable monitoring dashboards. The platform automatically recognises the metric naming patterns from your Rust application and enables sophisticated visualization and alerting capabilities.&lt;/p&gt;

&lt;p&gt;Here are some common ETL-specific metrics and their naming conventions you should follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;etl.load.duration&lt;/strong&gt;: measures the time spent loading CSV&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;etl.clean.duration&lt;/strong&gt;: tracks the performance of data cleaning stage&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;etl.aggregate.duration&lt;/strong&gt;: captures how long aggregation operation takes&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;etl.rows.processed&lt;/strong&gt;: shows record throughput and overall volume&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;etl.memory.peak&lt;/strong&gt;: reports peak memory consumption per stage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can configure dashboard for data pipeline monitoring like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Usage Over Time Dashboard&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"etl.memory.peak"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"group_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stage"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visualization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"line_chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1h"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stage-by-Stage Duration Analysis&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"etl.load.duration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"etl.clean.duration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"etl.aggregate.duration"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"avg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visualization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stacked_area"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"group_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Processing Throughput Tracking&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"etl.rows.processed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visualization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gauge"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These visualizations provide insights into processing performance trends, memory consumption patterns during different pipeline stages, and throughput characteristics that help with capacity planning and performance optimization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw4gd7iv4eo9sm20c9or.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw4gd7iv4eo9sm20c9or.png" alt="Betterstack Dashboard showing the average cpu usage time in the data pipeline" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g27wf3b6h9qx0v76s5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g27wf3b6h9qx0v76s5d.png" alt="Betterstack Dashboard showing the clean stage etl performance in the data pipeline" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These images show Betterstack monitoring dashboards tracking ETL pipeline performance, where the Telemetry interface displays Clean Time and Aggregate Time charts with regular periodic spikes indicating scheduled batch operations, while a separate CPU usage chart reveals sustained high utilization around 1,200-1,500T during data processing, providing essential visibility into pipeline performance and resource monitoring.&lt;/p&gt;

&lt;p&gt;With BetterStack's alerting capabilities, you can enable proactive monitoring by setting alerts for memory usage exceeding thresholds, processing duration degradation, or throughput drops below expected levels with integration into incident management systems for automated escalation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Development and Testing Setup
&lt;/h2&gt;

&lt;p&gt;So, how can you test and benchmark this pipeline on your own local machine with a real dataset? Running the full ETL process within our deployed web service isn't practical; web endpoints need fast responses, not long-running data processing jobs. Instead, we'll use Rust's feature flags to create a separate CLI for local, in-depth testing. This approach lets you experiment with different datasets, measure actual performance characteristics, and validate your observability setup before deploying to production.&lt;/p&gt;

&lt;p&gt;Feature flags enable different build targets for local development versus production deployment. This approach allows for comprehensive local testing with real datasets while maintaining production builds as lightweight and cost-effective as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure feature-based compilation in Cargo.toml:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[features]&lt;/span&gt;
&lt;span class="py"&gt;bench-cli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="py"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Local development implementation with full ETL processing:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(feature&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bench-cli"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;observability&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"polars-etl-benchmark-cli"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting Polars ETL benchmark with observability"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DATA_PATH"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"data/yellow_tripdata_2015-01.csv"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CSV file not found: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Set DATA_PATH environment variable or place file at default location"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;etl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PolarsETL&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;etl&lt;/span&gt;
        &lt;span class="nf"&gt;.load_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.clean_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.aggregate_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.save_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.as_secs_f64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ETL benchmark completed in {:.2} seconds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;etl&lt;/span&gt;&lt;span class="nf"&gt;.get_metrics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Performance metrics:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  {}: {:.2}s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="nf"&gt;.replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ETL processing error: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CLI workflow for benchmarking locally&lt;/strong&gt; with comprehensive logs and metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Process full dataset with detailed telemetry&lt;/span&gt;
&lt;span class="nv"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debug cargo run &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--features&lt;/span&gt; bench-cli

&lt;span class="c"&gt;# Use custom dataset location&lt;/span&gt;
&lt;span class="nv"&gt;DATA_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/your/dataset.csv &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;RUST_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info cargo run &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--features&lt;/span&gt; bench-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dataset loading via CLI&lt;/strong&gt; enables testing with different data characteristics. The CLI version processes actual CSV files and provides accurate performance measurements, memory usage patterns, and processing throughput data that inform production capacity planning.&lt;/p&gt;

&lt;p&gt;Local testing reveals performance characteristics that aren't visible in production monitoring. Memory allocation patterns during aggregation stages, processing bottlenecks with different dataset sizes, and error handling behaviour with malformed data files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Fast Pipelines with Precise Monitoring
&lt;/h2&gt;

&lt;p&gt;Rust with Polars delivers consistent performance that traditional Python workflows often lack. While Python pipelines struggle with unpredictable memory spikes, Rust provides predictable resource usage that scales linearly with dataset size. Moreover, compile-time error checking prevents the runtime failures that typically corrupt downstream analysis in dynamically-typed environments.&lt;/p&gt;

&lt;p&gt;Building on this foundation, OpenTelemetry adds precise monitoring with minimal performance overhead. This enables comprehensive dashboards and proactive alerts that surface issues before they impact data quality. Meanwhile, Shuttle eliminates the deployment complexity that often delays pipeline rollouts, providing zero-config OTLP export, automatic retry handling, and managed authentication out of the box.&lt;/p&gt;

&lt;p&gt;The business impact is substantial: teams ship data products 3x faster without infrastructure bottlenecks, process larger datasets with predictable costs, and maintain higher system reliability that directly improves user experience. Together, these tools create production-ready workflows that deliver fast processing through Rust and Polars, comprehensive visibility via OpenTelemetry, and frictionless deployment through Shuttle. This proactive monitoring approach prevents costly memory failures, enables accurate capacity planning, and catches performance bottlenecks before they affect end users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to build observable data pipelines? Try the complete example on Shuttle with one-click deployment:&lt;/strong&gt; &lt;a href="https://github.com/shuttle-hq/shuttle-examples/tree/main/axum/polars-otel-shuttle" rel="noopener noreferrer"&gt;&lt;strong&gt;Deploy Observable Pipeline&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Telemetry Issues in Rust Pipelines - FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: My metrics aren't showing up in BetterStack. What's wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; First, double-check your integration settings in the Shuttle console and verify your source token hasn't expired. Second, confirm your metric names in the code (e.g., &lt;code&gt;etl.load.duration&lt;/code&gt;) exactly match your dashboard queries—BetterStack requires precise field name matching. Finally, redeploy your application after making integration changes, as the OTLP exporter configuration updates during deployment. Test the flow by running &lt;code&gt;curl https://your-app.shuttle.app/benchmark&lt;/code&gt; and checking if logs appear in BetterStack's log stream before expecting dashboard data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Why are my spans missing or not connecting in distributed traces?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Check that your OpenTelemetry dependencies use compatible versions; mismatches between &lt;code&gt;tracing-opentelemetry&lt;/code&gt; and &lt;code&gt;opentelemetry&lt;/code&gt; crates can break span context propagation. Ensure your instrumented functions include appropriate resource attributes like &lt;code&gt;stage&lt;/code&gt; or &lt;code&gt;file&lt;/code&gt;, as missing context reduces trace value. Also, verify your tracer provider configuration includes your service name and resource attributes needed for filtering and grouping in trace analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: My pipeline works in production but fails during local testing. What should I check?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Start by validating CSV file locations and permissions—your application needs read access to data files and write access to results directories. Check that your dataset format is compatible with Polars CSV parsing, as schema mismatches or encoding issues can cause silent failures. Monitor memory consumption during testing since large datasets might exceed available memory during aggregation stages. Finally, debug your environment variable configuration, ensuring &lt;code&gt;DATA_PATH&lt;/code&gt; points to accessible file locations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How can I get better error information when my pipeline fails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Implement structured error logging that includes file paths, record counts, and processing stage information in error messages. This context enables rapid issue resolution by showing exactly where and why failures occur, rather than generic error messages that require extensive debugging to understand.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>datapipelines</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>A Hands-on Comparison of Best MCP Servers for Rust Developers</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Tue, 23 Sep 2025 15:38:13 +0000</pubDate>
      <link>https://dev.to/shuttle/a-hands-on-comparison-of-best-mcp-servers-for-rust-developers-598k</link>
      <guid>https://dev.to/shuttle/a-hands-on-comparison-of-best-mcp-servers-for-rust-developers-598k</guid>
      <description>&lt;p&gt;Model Context Protocol (MCP) servers boost developer productivity by working with an AI coding assistants like GitHub Copilot, Cursor, and Claude. With MCP having full access to the Rust project, the AI assistants are no longer limited to the currently open file; they can now follow instructions to execute commands, analyze the entire codebase, run tests, and automate workflows, making development faster.&lt;/p&gt;

&lt;p&gt;Most developers don't rely on a single MCP; instead, they use multiple servers, depending on their task. Each MCP comes with its own strengths and limitations.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through the best MCP servers for Rust developers. We will also compare the most popular MCP servers to break down what each server does best, and show you how to deploy your Rust applications directly from your IDE.&lt;/p&gt;

&lt;p&gt;Here is a quick overview of the comparison between the best MCP Servers&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCP Server&lt;/th&gt;
&lt;th&gt;Rust Integration&lt;/th&gt;
&lt;th&gt;AI Compatibility&lt;/th&gt;
&lt;th&gt;Security Setup&lt;/th&gt;
&lt;th&gt;Deployment Support&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context7 MCP&lt;/td&gt;
&lt;td&gt;Provides up-to-date, version-specific documentation for all libraries including Rust crates&lt;/td&gt;
&lt;td&gt;AI assistant can fetch real-time docs and code examples for accurate code generation&lt;/td&gt;
&lt;td&gt;Low risk, read-only access to documentation APIs&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Essential for preventing hallucinated APIs and outdated code examples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker MCP&lt;/td&gt;
&lt;td&gt;Good for developing containerized Rust applications.&lt;/td&gt;
&lt;td&gt;AI assistance can issue Docker commands through MCP&lt;/td&gt;
&lt;td&gt;Requires elevated privileges for certain operations, which can pose risks if not isolated&lt;/td&gt;
&lt;td&gt;Can build, package, and push Rust apps to a Docker Registry&lt;/td&gt;
&lt;td&gt;Suitable for those interested in or running Rust applications in Docker containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Server MCP&lt;/td&gt;
&lt;td&gt;Integrates with Rust code repositories and supports CI/CD workflows (GitHub Actions)&lt;/td&gt;
&lt;td&gt;The AI assistant can raise PRs, manage issues, and interact with GitHub workflows&lt;/td&gt;
&lt;td&gt;Requires GitHub auth token access with limited or supervised privileges&lt;/td&gt;
&lt;td&gt;Works effectively with GitHub Actions workflow&lt;/td&gt;
&lt;td&gt;Great for managing repositories and automating workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File System MCP Servers&lt;/td&gt;
&lt;td&gt;Provide direct access to Rust source code&lt;/td&gt;
&lt;td&gt;AI assistance has complete visibility of the Rust project&lt;/td&gt;
&lt;td&gt;Risky because AI assistant can read and modify project files. Sandbox is recommended&lt;/td&gt;
&lt;td&gt;Requires a custom script for deployment&lt;/td&gt;
&lt;td&gt;Useful for testing workflows that rely on code modifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory MCP Servers&lt;/td&gt;
&lt;td&gt;No direct Rust project support&lt;/td&gt;
&lt;td&gt;Helpful in maintaining conversational context across tasks&lt;/td&gt;
&lt;td&gt;Low risk since it operates only in memory&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;It is used in combination with other MCPs or systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browsing MCP Servers&lt;/td&gt;
&lt;td&gt;No direct Rust integration. This is useful for fetching Rust documentation and creating crates.io packages&lt;/td&gt;
&lt;td&gt;AI assistant can read and retrieve information from Rust documentation&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Great for referencing external Rust documentation and libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shuttle MCP&lt;/td&gt;
&lt;td&gt;Built specifically for Rust. It integrates perfectly into the Rust ecosystem&lt;/td&gt;
&lt;td&gt;It works well with AI assistants to manage and deploy Rust applications&lt;/td&gt;
&lt;td&gt;It handles secrets and other sensitive details securely on the Shuttle Platform&lt;/td&gt;
&lt;td&gt;It can deploy Rust applications directly from a developer's IDE&lt;/td&gt;
&lt;td&gt;It is helpful for Rust deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before we deploy our Rust applications directly from the IDE, let's understand what Model Context Protocol (MCP) is and what makes a good MCP for Rust developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Model Context Protocol (MCP)?
&lt;/h3&gt;

&lt;p&gt;Large Language Models (LLMs) such as GPT, Claude, and Gemini are powerful tools that significantly increase developer productivity. However, they share a fundamental limitation: their knowledge is restricted to the time of their training. For example, an LLM trained in early 2024 will not be aware of new frameworks, events, libraries, or any new information that emerges in 2025 or beyond unless it is updated. MCP was designed to close a critical gap in the AI coding assistant ecosystem. It allows AI assistants to go beyond code generation.&lt;/p&gt;

&lt;p&gt;MCP is an open standard that was introduced by Anthropic in November 2024. It enables LLMs to connect to various data sources, tools, and APIs, thereby fetching realtime information or performing tasks that exceed their built-in knowledge.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes a Good MCP for Rust Developers?
&lt;/h3&gt;

&lt;p&gt;When evaluating an MCP server for Rust development, it is essential to keep a few key criteria in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rust-Native Workflows:&lt;/strong&gt; Does the MCP work smoothly with Cargo and popular frameworks like Axum, Actix-Web, or Rocket? The best servers will integrate effectively with the existing Rust ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Assistant Compatibility:&lt;/strong&gt; Can your AI assistant access the MCP server, either through LLMs or a developer IDE? Developers should consider how the MCP will fit into their current workflow. Ideally, the MCP should support both IDE and LLM integrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and Access:&lt;/strong&gt; Security is a crucial factor when choosing the right MCP. Developers need to be confident that their secrets are kept safe and that the MCP server avoids unnecessary privilege escalation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup Complexity:&lt;/strong&gt; A good MCP server should be simple to configure and use. It shouldn't require complex installation steps, whether in an IDE or through an LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Capability:&lt;/strong&gt; Can the MCP server assist with end-to-end deployment, beyond just code generation? The best MCPs should be able to generate code, build, test, package, and even deploy Rust applications directly from an IDE.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these criteria in mind, let's look at the seven most common MCP servers Rust developers actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 Most Common MCP Servers for Rust Developers
&lt;/h2&gt;

&lt;p&gt;Here is a list of popular MCP servers that are particularly useful for Rust development.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Context7 MCP Server
&lt;/h3&gt;

&lt;p&gt;Context7 MCP Server addresses one of the most critical challenges in AI-assisted Rust development: outdated and hallucinated documentation. When working with Rust's rapidly evolving ecosystem, developers often encounter AI assistants that generate code based on year-old training data, leading to deprecated APIs, incorrect function signatures, and non-existent methods.&lt;/p&gt;

&lt;p&gt;Context7 solves this by providing real-time access to up-to-date, version-specific documentation directly from the source. For Rust developers, this means accurate code examples for popular crates like Axum, Tokio, Serde, and emerging libraries that weren't in the AI's training data.&lt;/p&gt;

&lt;p&gt;The main advantage is eliminating the frustration of debugging AI-generated code that uses outdated APIs. Instead of spending time fixing deprecated methods or non-existent functions, developers can trust that the AI assistant has access to current documentation and working code examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Docker MCP Server
&lt;/h3&gt;

&lt;p&gt;Docker MCP Server is ideal for Rust developers managing complex dependencies or building applications that require isolated environments. By running a Rust project inside a Docker container, the AI assistant can conveniently perform builds, run tests, and refactor code in a secure and reproducible way. Developers can easily reproduce builds and dependencies consistently without affecting the other parts of the codebase.&lt;/p&gt;

&lt;p&gt;Developers can also run commands inside the Docker container, such as &lt;strong&gt;cargo build&lt;/strong&gt;, with confidence that the results will be identical in both production and non-production environments. This consistency is helpful when resolving dependency-related build problems.&lt;/p&gt;

&lt;p&gt;Rust developers can leverage pairing Docker MCP with File system MCP to streamline their day-to-day coding activities and automate a smooth workflow. Docker MCP is best used for integration testing, Continuous Integration workflow, or sandbox experimentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. GitHub MCP Server
&lt;/h3&gt;

&lt;p&gt;Rust developers derive the most benefits from using the GitHub MCP server when their code repository is hosted on GitHub. This MCP server automates workflows such as setting up Continuous Integration (CI) pipelines with GitHub Actions and managing releases. Developers can ask their AI assistant to handle tasks such as bumping crate versions, generating and maintaining changelog files, and other routine repository management tasks.&lt;/p&gt;

&lt;p&gt;The GitHub MCP Server is tightly coupled to GitHub, which means that its full capabilities are only available within the GitHub ecosystem. Teams hosting their code on GitLab, Bitbucket, or other platforms may not find the GitHub MCP server as useful. The GitHub MCP Server is best used when paired with Docker MCP to provide a complete workflow from development to deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. File System MCP Server
&lt;/h3&gt;

&lt;p&gt;The File System MCP Server provides the AI assistant with direct access to read, write, and modify Rust project files. This allows Rust developers to perform tasks such as refactoring code, automatically fixing errors in the codebase, creating new features, and updating Rust files accordingly.&lt;/p&gt;

&lt;p&gt;When using the MCP server, developers should have measures in place to track which files that have been modified by the assistant. Combined with GitHub MCP server, version control can be implemented, making it easier to track the changes introduced by File system MCP. For example, an AI assistant could create a new feature, test it within a Docker container, and then set up a CI workflow using GitHub's MCP; this demonstrates how Docker, GitHub, and File System MCP can all work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Memory MCP
&lt;/h3&gt;

&lt;p&gt;Memory MCP stores project-specific knowledge across sessions, making it easier for an AI assistant to remember naming conventions, coding standards, and recurring compiler errors. For Rust developers working across different teams or modules, this helps to enforce consistency and maintain project wide standards.&lt;/p&gt;

&lt;p&gt;The main limitation is that the MCP is bound to a single context, which may restrict productivity in large projects that demand switching between multiple contexts. However, when paired with File System or Docker MCP, it can guide developers or an AI assistant during editing or refactoring, ensuring modifications align with the project standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Browsing MCP
&lt;/h3&gt;

&lt;p&gt;Browsing MCP gives the AI assistant realtime access to external information such as crates documentation, Rust official documentation, or usage examples. This MCP is beneficial when working on a task and needing to look up an example implementation or consult documentation without leaving the development workflow.&lt;/p&gt;

&lt;p&gt;The main limitation is that Browsing MCP can not modify the file system directly. When combined with other MCPs, such as File System or Docker MCP, the AI assistant can utilize the information it discovers to apply it to the project by creating code, updating files, or testing changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Shuttle MCP
&lt;/h3&gt;

&lt;p&gt;Shuttle MCP is tailored for deploying Rust applications, such as Axum, Actix, or Rocket. For a development team focused on rapid iteration, this MCP allows developers to deploy the application quickly from code to a live environment. Developers can concentrate on writing code while Shuttle handles server setup, SSL certificate management and deployment.&lt;/p&gt;

&lt;p&gt;Shuttle focuses on Rust projects and it integrates smoothly with Rust ecosystem. When combined with Docker and File System MCPs, developers can build and test their Rust projects locally. Then deploy to live environment with Shuttle, providing a smooth and end to end workflow&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges of Managing Multiple MCPs
&lt;/h2&gt;

&lt;p&gt;Using individual MCPs can help accelerate developer productivity, but managing multiple MCPs simultaneously introduces additional complexity. Here are some of the challenges I have encountered while working with various MCPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Management and Performance
&lt;/h3&gt;

&lt;p&gt;When working with multiple MCP servers, there is always the possibility that they will compete for system resources such as CPU and memory. In large Rust projects, this competition can negatively impact IDE performance. It becomes a significant bottleneck when projects rely heavily on multiple MCP servers.&lt;/p&gt;

&lt;p&gt;To ensure you get the most out of these MCPs while still maintaining top performance in your Rust projects, here are a few best practices to follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitor and limit resource usage:&lt;/strong&gt; Carefully track CPU and memory usage and limit the number of active MCPs to prevent overload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocate CPU and memory per MCP:&lt;/strong&gt; Assign specific resources to each MCP server to ensure no single server dominates system resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security and Authentication Complexity
&lt;/h3&gt;

&lt;p&gt;As a developer, when I want to use an MCP server, each server requires its own authentication and security setup. When multiple MCP servers, such as Docker, GitHub, file system, and browsing MCPs, are running simultaneously, managing all the secrets, API keys, access tokens, and security configurations can become challenging. If one MCP is compromised or becomes vulnerable, it could put the entire MCP setup at risk.&lt;/p&gt;

&lt;p&gt;To make multiple MCPs safe for use, you can do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implement strict isolation&lt;/strong&gt; between servers to prevent a compromised MCP from affecting others. This can be achieved by:

&lt;ul&gt;
&lt;li&gt;Running each MCP on its own container or virtual machine.&lt;/li&gt;
&lt;li&gt;Storing secrets or API Keys in a dedicated, secure vault with a separate namespace, so that if one secret is compromised, it does not affect others.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Perform regular secret rotation&lt;/strong&gt;, for example, yearly or quarterly, to minimize the risk of leaked credentials.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Continuously monitor the environment&lt;/strong&gt; for any suspicious activity.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The real challenge arises when developers need to use multiple MCP servers simultaneously, such as Docker and GitHub. Each server has its own authentication, logs, and configuration, which can quickly turn into an overwhelming experience. Without a unifying layer, developers end up spending more time managing connections than actually building.&lt;/p&gt;

&lt;h2&gt;
  
  
  How MCP Aggregators and API Gateways Solve These Problems
&lt;/h2&gt;

&lt;p&gt;As a developer, I want a solution that enables me to leverage multiple MCP servers with my AI assistant, unifying different tools and data sources into a single, seamless workflow. Platforms like &lt;a href="http://Lunar.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Lunar.dev&lt;/strong&gt;&lt;/a&gt;, along with other MCP aggregators such as &lt;a href="http://Solo.io" rel="noopener noreferrer"&gt;&lt;strong&gt;Solo.io&lt;/strong&gt;&lt;/a&gt; and &lt;strong&gt;MCP Bridge&lt;/strong&gt;, help tackle the complexity of managing multiple MCP servers. These platforms act as middleware, allowing you to configure various MCPs through a single interface.&lt;/p&gt;

&lt;p&gt;Here is how MCP aggregators help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified logging:&lt;/strong&gt; Developers can view a stream of logs from multiple MCP servers, which is more helpful in debugging than viewing logs from a single MCP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security monitoring and access control:&lt;/strong&gt; Aggregators provide fine-grained access control, making it easier to identify which permissions are granted to a specific developer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance optimization and caching:&lt;/strong&gt; Frequently accessed or requested results can be cached at the aggregator level, reducing latency, improving response times for developers, and ultimately saving time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rust developers can increase their productivity by integrating AI-assisted workflows into their ecosystem. These aggregators add tremendous value by simplifying integration, reducing overhead, and maintaining a secure and performant development environment, allowing engineers to focus on building code.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Shuttle Solves the Deployment Problem
&lt;/h3&gt;

&lt;p&gt;Rust developers are currently facing a significant challenge with deployment. With the assistance of AI in accelerating the generation of code, deploying Rust projects to live environments often requires extra steps compared to other languages like Python or Node JS, which benefits from a mature Platform as a Service (PaaS) solutions, strong community support and extensive resources. On the other hand, Rust ecosystem is still evolving with fewer tutorials, deployment focused tools and best practices available.&lt;/p&gt;

&lt;p&gt;You may have generated clean Axum handlers, written comprehensive tests, and even have users waiting for your feature. Yet the moment you are ready to deploy, you are forced into manual deployment processes.&lt;/p&gt;

&lt;p&gt;The current deployment workflows for Rust applications require developers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up cloud infrastructure&lt;/li&gt;
&lt;li&gt;Set up monitoring and logging&lt;/li&gt;
&lt;li&gt;Handle SSL certificates and domain configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There should be a workflow that allows developer to deploy their Rust project directly from their IDE.&lt;/p&gt;

&lt;p&gt;Shuttle MCP enables Rust developers to deploy applications directly from their IDE by integrating with Shuttle CLI, which requires a separate installation. This approach boosts productivity by removing the manual steps involved in traditional deployment. Shuttle packages, compiles, and deploys your application automatically, so that you can stay focused on building.&lt;/p&gt;

&lt;p&gt;Unlike other solutions that requires complex scripts or Infrastructure as a Code (IaC) configurations which rely on YAML files or provider specific configuration. Shuttle follows an Infrastructure from Code (IfC) approach, which means that your infrastructure is defined directly in your Rust codebase alongside with the application logic.&lt;/p&gt;

&lt;p&gt;You can ask your AI assistant to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy your Axum API to the live environment&lt;/li&gt;
&lt;li&gt;Check the logs of the deployed service&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hands-on Example: Build and Deploy a Snippet Sharing API with Shuttle
&lt;/h2&gt;

&lt;p&gt;In this hands-on section, we'll build a Rust + Axum API that powers a simple code snippet sharing service (similar to Pastebin) and demonstrate how to fetch documentation with Context7 MCP Server, create a pull request with GitHub MCP Server and deploy the Rust code using Shuttle MCP Server, with Cursor AI assistance.&lt;/p&gt;

&lt;p&gt;The API will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoints to create and retrieve snippets&lt;/li&gt;
&lt;li&gt;Filtering snippets by programming language&lt;/li&gt;
&lt;li&gt;Clean, shareable URLs with 8-character IDs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Endpoints
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/health&lt;/td&gt;
&lt;td&gt;Health check endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/snippets&lt;/td&gt;
&lt;td&gt;Create a new code snippet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/snippets&lt;/td&gt;
&lt;td&gt;List all snippets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/snippets/{id}&lt;/td&gt;
&lt;td&gt;Get a single snippet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/snippets/{id}&lt;/td&gt;
&lt;td&gt;Delete a snippet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Step 1: Setting Up Your Environment: Install Rust and Shuttle CLI
&lt;/h4&gt;

&lt;p&gt;Firstly, ensure you have the following software running:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Rust and Cargo:&lt;/strong&gt; For local development and testing.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Shuttle CLI:&lt;/strong&gt; For deployment into a live environment. You can install it via Cargo, or use the installation script by following this link: &lt;a href="https://docs.shuttle.dev/getting-started/installation" rel="noopener noreferrer"&gt;Shuttle CLI Installation.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cursor:&lt;/strong&gt; An AI coding assistant. We will use it to interact with MPC Servers (Context7, GitHub and Shuttle) for documentation lookup, creating PRs and deploying the Rust application.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 2: Log in to Shuttle
&lt;/h4&gt;

&lt;p&gt;Authenticate your Shuttle account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This connects your CLI to your Shuttle account, enabling authorized deployments.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Setting Up Our Project
&lt;/h4&gt;

&lt;p&gt;Next, we will create our Rust project. Below is an example of the Cargo.toml configuration. You can also generate the below file &lt;code&gt;Cargo.toml&lt;/code&gt; by running the shuttle command &lt;code&gt;shuttle init&lt;/code&gt; . Running the command will initializes a new Shuttle project in your current directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"code-snippet-sharing-app"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"macros"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rt-multi-thread"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;shuttle-axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.57.0"&lt;/span&gt;
&lt;span class="py"&gt;shuttle-runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.57.0"&lt;/span&gt;
&lt;span class="py"&gt;shuttle-shared-db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.57.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sqlx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="py"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"serde"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;chrono&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"serde"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;nanoid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.4"&lt;/span&gt;
&lt;span class="py"&gt;sqlx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"runtime-tokio-rustls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"chrono"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Build the Rust Code Locally
&lt;/h4&gt;

&lt;p&gt;Build the Rust code by running this command below to build the Rust project to confirm that there are no errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Run the Rust Code Locally
&lt;/h4&gt;

&lt;p&gt;Run the Rust code by running this command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Deploy your app
&lt;/h4&gt;

&lt;p&gt;Now, let's deploy your application using Shuttle and see how it makes deployment easier by handling all the heavy lifting for you. We will be deploying the application using Shuttle MCP Server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The URLs used in the following steps are examples. When you deploy your own application, Shuttle will provide you with a unique URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From your cursor IDE. Run the command on the prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#Prompt &lt;/span&gt;
Deploy the Rust code

Use the Shuttle MCP server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkn5zs81idfszziqmn686.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkn5zs81idfszziqmn686.png" alt="How to deploy Rust application using Shuttle MCP Server" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpl8mubiepz1f983y5ud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpl8mubiepz1f983y5ud.png" alt="Shuttle deployment console showing successful deployment with generated URL" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI assistant will deploy the application and a link will be generated. You can access your application with the provided link. &lt;/p&gt;

&lt;p&gt;For instance, in this case, the link is &lt;code&gt;https://code-snippet-share-ow2x.shuttle.app&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Shuttle will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package your code&lt;/li&gt;
&lt;li&gt;Compile it in the cloud&lt;/li&gt;
&lt;li&gt;Provision infrastructure&lt;/li&gt;
&lt;li&gt;Manage SSL certificate&lt;/li&gt;
&lt;li&gt;Deploy your app to a live environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 6: Test Your App Deployed Using Shuttle: Health Endpoint
&lt;/h4&gt;

&lt;p&gt;Let's test the application we deployed with Shuttle to ensure all the endpoints are working correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health Endpoint:&lt;/strong&gt; &lt;code&gt;https://code-snippet-share-ow2x.shuttle.app/health&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can use curl from the command line or a tool like Postman. In this example, we will use Postman.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakr9bzj8wfkgd17bfmh7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakr9bzj8wfkgd17bfmh7.png" alt="Postman request testing the health endpoint showing successful response" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 8: Test Your App Deployed Using Shuttle: POST Snippet Endpoint
&lt;/h4&gt;

&lt;p&gt;Send a POST request to &lt;code&gt;https://code-snippet-share-ow2x.shuttle.app/snippets&lt;/code&gt; with a JSON payload to create a new snippet record.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslm1fzmxo8oup12rkmv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslm1fzmxo8oup12rkmv4.png" alt="Postman POST request creating a new code snippet with JSON payload" width="800" height="506"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A Hands-on Comparison of Best MCP Servers for Rust Developers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rust"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shuttle MCP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Explore Shuttle MCP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in_hours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_public"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 8: Test Your App Deployed Using Shuttle: GET APl Snippet Endpoint
&lt;/h4&gt;

&lt;p&gt;Send a GET request to &lt;code&gt;https://code-snippet-share-ow2x.shuttle.app/snippets&lt;/code&gt; to retrieve all snippets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv15x2uecsc268o31quz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv15x2uecsc268o31quz.png" alt="Postman request to retrieve all snippets" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 9: Test Your App Deployed Using Shuttle: GET a Single Snippet Endpoint
&lt;/h4&gt;

&lt;p&gt;Send a GET request to &lt;code&gt;https://code-snippet-share-ow2x.shuttle.app/snippets/qjs0BzAq&lt;/code&gt; to retrieve a specific snippet by its ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix7iiw9aujtdwy6xyb7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix7iiw9aujtdwy6xyb7r.png" alt="Postman request to retrieve a single snippet" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 10: Use GitHub MCP Server
&lt;/h4&gt;

&lt;p&gt;We will explore how to use the GitHub MCP Server to create a Pull Request (PR) and manage the Rust project on GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prompt&lt;/span&gt;

Raise a PR with my new changes

Using GitHub MCP Server 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nri1kc0mmajpg11svtq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nri1kc0mmajpg11svtq.png" alt="Using GitHub MCP Server to raise PR" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 11: Use Context7 MCP Server
&lt;/h4&gt;

&lt;p&gt;We will explore how to use Context7 MCP Server to ask questions about the Rust code and access documentation related to the project. In the example below, we demonstrate how to use Context7 MCP Server to ask a question and reference documentation "&lt;em&gt;I need to reference a Rust documentation on how i can persistent snippet data instead of using in memory HashMap&lt;/em&gt;".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prompt &lt;/span&gt;

I need to reference a Rust documentation on how i can persistent snippet data instead of using &lt;span class="k"&gt;in &lt;/span&gt;memory HashMap.

Using context7 MCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7in7wbisa8d7z9utxy0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7in7wbisa8d7z9utxy0m.png" alt="How to use context7 MCP Server" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The MCP ecosystem is opening new possibilities for Rust developers. Whether you are managing repositories with GitHub MCP, restructuring projects through the Filesystem MCP, or streamlining research with the Browser MCP, each option reduces friction in its own way. By combining multiple MCP servers, AI assistants can support the entire application lifecycle, allowing developers to focus on writing Rust instead of wrestling with complex infrastructure tools.&lt;/p&gt;

&lt;p&gt;Shuttle simplifies deployment, letting you build features, test ideas, and ship production-ready applications in minutes.&lt;/p&gt;

&lt;p&gt;Ready to try this out for yourself? Run the following command to get started immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/code-snippet-sharing-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other ways to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://console.shuttle.dev/" rel="noopener noreferrer"&gt;Sign up for Shuttle&lt;/a&gt; to start building with Shuttle.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shuttle-hq/shuttle-examples/tree/main/axum/code-snippet-sharing-app" rel="noopener noreferrer"&gt;Clone the repository&lt;/a&gt; used in this article to explore how to build with Shuttle.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.shuttle.dev" rel="noopener noreferrer"&gt;Learn more about Shuttle&lt;/a&gt; to get started quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever MCP fits your workflow, &lt;strong&gt;Shuttle&lt;/strong&gt; makes deploying Rust applications effortless.&lt;/p&gt;

</description>
      <category>shuttle</category>
      <category>mcp</category>
      <category>rust</category>
      <category>docker</category>
    </item>
    <item>
      <title>Best AI Coding Tools for Rust Projects: IDEs vs Terminals</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Thu, 11 Sep 2025 14:04:37 +0000</pubDate>
      <link>https://dev.to/shuttle/best-ai-coding-tools-for-rust-projects-ides-vs-terminals-3md2</link>
      <guid>https://dev.to/shuttle/best-ai-coding-tools-for-rust-projects-ides-vs-terminals-3md2</guid>
      <description>&lt;p&gt;As Rust developers, we tend to rely on familiar setups like Visual Studio Code (VS Code) or the terminal because they offer the speed and control we need. AI coding tools extend these workflows but approach them in different ways. Some plug directly into IDEs, while others work in the terminal. If you are building Rust projects, the real question is which of these tools are actually useful in practice.&lt;/p&gt;

&lt;p&gt;To find out, we tested five popular AI coding assistants by building the same Rust HTTP server with Axum. We compared how quickly they generated code, the quality of what they produced, and how well they fit into everyday Rust workflows.&lt;/p&gt;

&lt;p&gt;Because each tool outputs code differently, we also needed a way to deploy their results without switching stacks or starting from scratch. We will walk you through how we solved that as well.&lt;/p&gt;

&lt;p&gt;The table below summarizes the key differences between IDE-based and terminal-based AI coding tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;IDE-Based Tools&lt;/th&gt;
&lt;th&gt;Terminal-Based Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;Deep editor integration with real-time suggestions&lt;/td&gt;
&lt;td&gt;Command-line focused with file system awareness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow&lt;/td&gt;
&lt;td&gt;Seamless coding experience within a familiar environment&lt;/td&gt;
&lt;td&gt;Context-switching between the terminal and the editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context Awareness&lt;/td&gt;
&lt;td&gt;Full project context with syntax highlighting&lt;/td&gt;
&lt;td&gt;File-based context with smart project understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Minimal - extends existing IDE workflow&lt;/td&gt;
&lt;td&gt;Moderate - requires learning CLI commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collaboration&lt;/td&gt;
&lt;td&gt;Individual developer focused&lt;/td&gt;
&lt;td&gt;Often better for pair programming and code review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customization&lt;/td&gt;
&lt;td&gt;Limited to IDE extension capabilities&lt;/td&gt;
&lt;td&gt;Highly customizable through configuration files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  AI Coding Tools: IDEs vs. Terminals
&lt;/h2&gt;

&lt;p&gt;Choosing an AI coding tool is about features, as well as matching the tool to how you think and work. Some developers thrive with constant AI suggestions appearing in their editor. Others find this distracting and prefer tools that allow them to describe a problem and then generate complete solutions. The divide between IDE-integrated and terminal-based tools reflects these different working styles.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are IDE-based AI Coding Tools?
&lt;/h3&gt;

&lt;p&gt;IDE-based AI coding tools integrate directly into graphical Integrated Development Environments (IDEs). They enhance the development experience with intelligent code completion, refactoring, optimization, and debugging. Many also bring real-time suggestions, multi-file editing, project-wide refactoring, and built-in chat panels for natural language queries.&lt;/p&gt;

&lt;p&gt;With IDE-based tools, you can expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Graphical Interface: Work inside a visual IDE with inline code suggestions, multiple views, and interactive chat panels for prompts.&lt;/li&gt;
&lt;li&gt;Context-Based Suggestions: Get completions and refactoring ideas based on the IDE's deep understanding of your codebase.&lt;/li&gt;
&lt;li&gt;Deep Integration: Leverage the IDE's ecosystem, including project navigation, file management, and version control, all enhanced by AI.&lt;/li&gt;
&lt;li&gt;Higher Resource Usage: Keep in mind that graphical interfaces and IDE overhead usually demand more system resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the most popular IDE-based AI coding tools are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor&lt;/li&gt;
&lt;li&gt;Windsurf (formerly Codeium)&lt;/li&gt;
&lt;li&gt;VS Code + GitHub Copilot&lt;/li&gt;
&lt;li&gt;Kiro by AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cursor:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cursor is an AI-first code editor built with AI assistance at its core. It takes the familiar foundation of VS Code and layers in advanced AI capabilities, so you can work faster and smarter without giving up your usual workflow. If you've used VS Code before, you'll instantly recognize the interface and navigation, but you'll also see new tools that make coding feel collaborative.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native AI Chat Interface: Talk to AI right inside your editor.&lt;/li&gt;
&lt;li&gt;Multi-File Editing with AI: Edit across multiple files in one request.&lt;/li&gt;
&lt;li&gt;Codebase-Wide Understanding: AI understands your whole project.&lt;/li&gt;
&lt;li&gt;Custom AI Model Selection: Pick the AI engine that fits your needs.&lt;/li&gt;
&lt;li&gt;Real-Time Code Generation: Code gets written as you type.&lt;/li&gt;
&lt;li&gt;Smart Autocomplete: Smarter, context-aware autocomplete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zvvwaeic46bi4zf0yn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zvvwaeic46bi4zf0yn3.png" alt="Cursor IDE" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windsurf (formerly Codeium):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Windsurf, rebranded from Codeium, is an AI-powered IDE forked from VS Code and designed to weave AI assistance throughout the entire code development process. Compared to Cursor, Windsurf leans toward a more polished and minimal aesthetic. You can think of it as Apple-like refinement compared to Microsoft's functionality-first approach.&lt;/p&gt;

&lt;p&gt;One of Windsurf's standout features is &lt;strong&gt;Cascade&lt;/strong&gt;, which enables true agentic programming. With Cascade, the AI can understand your project across multiple files and take coordinated action without requiring you to micromanage every step.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intelligent Code Refactoring: Automatically clean up and restructure code without breaking functionality.&lt;/li&gt;
&lt;li&gt;Architecture-Aware Suggestions: Get recommendations that fit your project's overall design.&lt;/li&gt;
&lt;li&gt;Cross-Language Project Support: Seamlessly work across multiple programming languages.&lt;/li&gt;
&lt;li&gt;Advanced Debugging Assistance: Understand errors and get targeted, multi-file fixes.&lt;/li&gt;
&lt;li&gt;Code Quality Insights: Instant feedback on clarity, maintainability, and best practices.&lt;/li&gt;
&lt;li&gt;Deep Static Analysis Integration: Catch hidden bugs, security risks, and performance issues early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5xnqeb7xe59ecj0gzlu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5xnqeb7xe59ecj0gzlu.png" alt="Windsurf IDE" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code + GitHub Copilot:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub Copilot is one of the most widely used AI coding assistants. It integrates directly into VS Code as well as other popular IDEs and works in real time to suggest code, generate functions from natural language prompts, and explain existing code. Whether you're writing in JavaScript, Python, Go, or working across multiple languages, Copilot blends seamlessly into your environment while tying neatly into the broader GitHub ecosystem.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-Powered Code Suggestions: Get instant code completions from single lines to full functions.&lt;/li&gt;
&lt;li&gt;Natural Language to Code: Describe it in plain English, and let AI write the code.&lt;/li&gt;
&lt;li&gt;Chat and Inline Assistance (Copilot Chat): Ask coding questions and get inline help without leaving your editor.&lt;/li&gt;
&lt;li&gt;Code Explanation and Documentation: Turn complex code into clear explanations.&lt;/li&gt;
&lt;li&gt;Test and Code Refactoring Support: Auto-generate tests and improve code readability safely.&lt;/li&gt;
&lt;li&gt;Multi-Language and Framework Support: Works across dozens of languages and frameworks.&lt;/li&gt;
&lt;li&gt;Integration with GitHub Ecosystem: Easily connects with repos, PRs, and Actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2h595l6h4y8bswflf8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2h595l6h4y8bswflf8.png" alt="VS Code + GitHub Copilot" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kiro by AWS:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kiro is an AI-native IDE built by AWS and launched in 2025. It was designed to solve the challenges of "vibe coding" by introducing structured, spec-driven development. With this approach, Kiro generates a requirements document that includes user stories, mermaid diagrams, acceptance criteria, and more about what you want to build. You can review the spec, tweak it, and customize it to your needs before writing a single line of code. This way, you don't have to keep prompting over and over to get what you're looking for.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spec-Driven Development: Work from a single spec file that acts as your source of truth. AI agents generate, maintain, and evolve your code directly from it.&lt;/li&gt;
&lt;li&gt;Customizable Agent Behavior: Fine-tune how agents operate using steering configs in .kiro/steering/. Set rules like avoiding blocking calls, enforcing structured logging, or running tests on every commit to keep quality consistent.&lt;/li&gt;
&lt;li&gt;Automation Hooks: Add event-based automations to streamline your workflow. For example, regenerate scaffolding when specs change or trigger end-to-end tests when a pull request is opened.&lt;/li&gt;
&lt;li&gt;Multimodal Chat Interface: Collaborate with AI through text, code, and other formats. Get natural help with debugging, explanations, and brainstorming.&lt;/li&gt;
&lt;li&gt;Agentic Programming: Let AI agents handle multi-step tasks. They can plan projects, update specs, and keep changes synced across your codebase.&lt;/li&gt;
&lt;li&gt;AWS Integration: Connect seamlessly with AWS services for deployment, monitoring, and scaling so your projects move smoothly into production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sk2hdm6pu1nz9xh67yr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sk2hdm6pu1nz9xh67yr.png" alt="Kiro IDE" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key difference between IDE-based AI coding tools is in their approach. Cursor, Windsurf, and Copilot focus on real-time code suggestions and inline assistance for quick iteration. Kiro, on the other hand, takes a different path with agentic, spec-driven workflows that emphasize production readiness and cut down on ad-hoc "vibe coding" chaos. This makes it more agent-led and customizable, giving teams the guardrails and automations they need instead of just editor-centric features.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are Terminal-Based AI Coding Tools?
&lt;/h3&gt;

&lt;p&gt;Terminal-based AI coding tools run entirely in the command-line interface (CLI), letting you work with AI models through simple commands and prompts. If you prefer a lightweight, text-driven workflow and often rely on Git or other version control systems, these tools will feel right at home.&lt;/p&gt;

&lt;p&gt;With terminal-based tools, you can expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command Line Interface: Operates fully in the terminal with commands and prompts, often with little or no GUI.&lt;/li&gt;
&lt;li&gt;Lightweight and Fast: Uses minimal system resources, making it ideal for low-spec machines or headless environments such as servers.&lt;/li&gt;
&lt;li&gt;Direct LLM Access: Connect directly to large language models (LLMs) like Claude, GPT, or Gemini, with support for multiple providers.&lt;/li&gt;
&lt;li&gt;Git Integration: Streamline version control workflows by automatically committing changes to Git.&lt;/li&gt;
&lt;li&gt;Automation-Focused: Excel at automating repetitive tasks, running tests, and executing commands without leaving the terminal.&lt;/li&gt;
&lt;li&gt;Steeper Learning Curve: Requires comfort with command-line workflows and provides less visual feedback compared to IDE-based tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Popular terminal-based AI coding tools include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code&lt;/li&gt;
&lt;li&gt;Aider&lt;/li&gt;
&lt;li&gt;Gemini CLI&lt;/li&gt;
&lt;li&gt;OpenAI Codex CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Claude Code:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code is an AI coding assistant built to run directly in your terminal, designed for developers who want a hands-on but intelligent coding partner. You can delegate complex tasks to it, and it will plan, execute, and explain its work step by step. Whether you're writing new code, testing, debugging, or exploring a large codebase, Claude Code acts as an agentic AI that can navigate files, run multi-step processes, and integrate with your existing tools through natural conversation.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Natural Language Task Description: Describe what you want in plain English, and Claude handles it.&lt;/li&gt;
&lt;li&gt;File System Awareness: Understands and edits your project's files in context.&lt;/li&gt;
&lt;li&gt;Multi-Step Task Execution: Plans and completes tasks across multiple files seamlessly.&lt;/li&gt;
&lt;li&gt;Flexible Configuration Management: Set up coding preferences and project rules using dotfile-style configuration with claude.md files. Configure global settings in your home directory, project-specific rules in your project root, or granular settings in subfolders for large projects. This hierarchical approach lets you define coding standards, architectural patterns, and team conventions that Claude will automatically follow.&lt;/li&gt;
&lt;li&gt;Integration with Development Tools: Works smoothly with editors, build tools, tests, and version control.&lt;/li&gt;
&lt;li&gt;Conversational Debugging: Debug interactively with step-by-step guidance.&lt;/li&gt;
&lt;li&gt;Code Explanation and Documentation: Turn complex code into clear explanations and documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi82n1hxbaony1j76rrf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi82n1hxbaony1j76rrf.png" alt="Claude Code" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aider:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aider is an open-source AI pair programming assistant built to work hand-in-hand with Git. Unlike other coding tools that simply suggest snippets, Aider operates inside your version control workflow, making it especially powerful for collaborative and production-grade projects. It uses large language models to understand your codebase, propose intelligent changes, and keep everything neatly tracked in commits.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git-Aware Editing: Understands your Git context and suggests edits easily.&lt;/li&gt;
&lt;li&gt;Multi-file Coordinated Changes: Update multiple files at once for consistent refactoring.&lt;/li&gt;
&lt;li&gt;Automatic Commit Messages: Generate clear, descriptive commits automatically.&lt;/li&gt;
&lt;li&gt;Support for Multiple AI Models: Choose the AI model that fits your project and workflow.&lt;/li&gt;
&lt;li&gt;Real-Time Collaboration: Get live, pair-programmer-style coding suggestions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxys4hkzrwnzyn30pskbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxys4hkzrwnzyn30pskbt.png" alt="Aider" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini CLI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt; is Google's lightweight, open-source AI coding agent that brings Gemini models (like Gemini 2.5 Pro) directly into your terminal. With an ascended context window of 1 million tokens, it can analyze entire codebases at once and handle complex workflows with ease.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Massive Context Window: Powered by Gemini 2.5 Pro, with support for up to 1 million tokens. This gives the AI a deep understanding of your codebase and enables full project analysis.&lt;/li&gt;
&lt;li&gt;Real-time Web Search and Diagrams: Look up information on the web instantly and generate diagrams on the fly to speed up debugging and feature development.&lt;/li&gt;
&lt;li&gt;Open-Source Flexibility: Fully open-source under Apache 2. You can customize it, extend it, and contribute back to the community.&lt;/li&gt;
&lt;li&gt;Generous Free Tier: Get 60 requests per minute and 1,000 requests per day for free with just a personal Google account.&lt;/li&gt;
&lt;li&gt;Security and Sandboxing: Runs in a secure, sandboxed environment with network restrictions, ensuring safe code execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiap5c1hn26cdyu8mko1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiap5c1hn26cdyu8mko1m.png" alt="Gemini CLI" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI Codex CLI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI Codex CLI is an open-source, lightweight coding agent that brings ChatGPT-level reasoning to your terminal. Powered by OpenAI's o3 and o4-mini models, it emphasizes local execution, privacy, and multimodal input, letting you handle tasks like code generation, file manipulation, and test execution directly from the terminal.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multimodal Input: Supports text, screenshots, and diagrams to generate or edit code, making feature implementation faster and more intuitive.&lt;/li&gt;
&lt;li&gt;Local Privacy: Runs entirely on your machine, keeping your source code private unless you choose to share it.&lt;/li&gt;
&lt;li&gt;Flexible Approval Modes: Choose from Suggest, Auto-Edit, and Full Auto modes for different levels of control over code changes and command execution.&lt;/li&gt;
&lt;li&gt;Open-Source and Community-Driven: Encourages community contributions and supports multiple AI providers like Gemini and OpenRouter via the Chat Completion API.&lt;/li&gt;
&lt;li&gt;Sandbox Security: Executes commands in secure, sandboxed environments using Docker on Linux and Apple Seatbelt on macOS to ensure safe operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz5cd055nip15aswyjt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz5cd055nip15aswyjt5.png" alt="OpenAI Codex CLI" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the details of both IDE-based and terminal-based AI coding tools covered, let's now explore why they behave differently when you put them to work on a Rust project.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes These Categories Behave Differently in Rust?
&lt;/h3&gt;

&lt;p&gt;Rust's strict type system, borrow checker, and focus on safety make AI coding tools behave differently depending on how they integrate with your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDE-based tools&lt;/strong&gt; like Cursor, Windsurf, and GitHub Copilot hook directly into the Rust Language Server Protocol (LSP) through rust-analyzer. This means you get real-time, type inference, inline diagnostics, and borrow checker suggestions. The immediate feedback helps catch ownership issues or lifetime mismatches early, reducing compile cycles and making it easier to write idiomatic Rust code. These tools shine in visual debugging and refactoring, using the IDE's deeper understanding of Rust's semantics to speed up iteration in larger projects.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros and Cons of IDE-Based AI Tools in Rust Projects
&lt;/h4&gt;

&lt;p&gt;Below are some of the benefits you get when you use IDE-based AI tools in your Rust projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong support for multi-file refactoring and debugging, reducing time on ownership issues.&lt;/li&gt;
&lt;li&gt;Visual feedback and multi-view interfaces make navigating complex Rust projects easier.&lt;/li&gt;
&lt;li&gt;Deep integration with rust-analyzer provides context-aware fixes for the borrow checker and type errors.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless real-time suggestions and autocomplete help maintain flow during Rust's error-prone early stages.&lt;br&gt;
While the visuals make it ideal for building and debugging large-scale projects, they still come with some limitations:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reliance on a GUI makes them less ideal for headless or remote Rust environments, such as servers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Higher resource consumption, which can slow things down on lower-spec machines during Rust compilations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In contrast to IDE-based tools, &lt;strong&gt;terminal-based tools&lt;/strong&gt; such as Claude Code and Aider lean on command-line interactions and external commands like &lt;code&gt;cargo check&lt;/code&gt; or &lt;code&gt;cargo build&lt;/code&gt; for validation. Instead of inline suggestions, they generate suggestions on-demand through prompts, which takes an agentic approach of planning and applying multi-file changes or automations in one go. This makes them powerful for strategic code generation or bulk edits, though you'll likely need more manual builds to catch errors. They're lightweight and well-suited for server-side or low-resource development environments, but can feel less intuitive when dealing with Rust's more intricate ownership and lifetime rules without visual aids.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros and Cons of Terminal-Based AI Tools in Rust Projects
&lt;/h4&gt;

&lt;p&gt;Below are some of the benefits you get when you use terminal-based AI tools in your Rust projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct access to LLMs for in-depth explanations of Rust concepts without IDE overhead.&lt;/li&gt;
&lt;li&gt;Strong reasoning capabilities for planning Rust architectures or algorithms.&lt;/li&gt;
&lt;li&gt;Lightweight and fast, making them great for Rust development on servers or low-power devices.&lt;/li&gt;
&lt;li&gt;Well-suited for automation and Git-integrated workflows, including running builds and tests via commands.
While they are fast and less resource-intensive, they also come with some drawbacks:&lt;/li&gt;
&lt;li&gt;Less immediate feedback, as validation depends on manual runs of cargo tools, which can slow iteration.&lt;/li&gt;
&lt;li&gt;Steeper learning curve, since you'll need comfort with the CLI to handle Rust's verbose error messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hands-On: Building a Rust API with Each Tool
&lt;/h2&gt;

&lt;p&gt;Now it's time to put these tools to the test by building a task management API in Rust and seeing how each one handles the challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project Scope:&lt;/strong&gt; Five REST endpoints (&lt;code&gt;create_task&lt;/code&gt;, &lt;code&gt;get_task&lt;/code&gt;, &lt;code&gt;list_tasks&lt;/code&gt;, &lt;code&gt;update_task&lt;/code&gt;, &lt;code&gt;delete_task&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; In-memory store (no database, state held in-memory with a &lt;code&gt;HashMap&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toolchain:&lt;/strong&gt; Rust 2024 edition, without pinned crate versions (&lt;code&gt;axum&lt;/code&gt;, &lt;code&gt;tokio&lt;/code&gt;, &lt;code&gt;serde&lt;/code&gt;, &lt;code&gt;uuid&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluation Method:&lt;/strong&gt; Each tool was tested with the same starting prompt. We recorded the generated code, its completeness and accuracy, and its integration into the Rust workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Building with Cursor&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We opened a folder &lt;code&gt;rust-tasks-cursor&lt;/code&gt;in Cursor, launched the &lt;strong&gt;AI chat panel&lt;/strong&gt; (Cmd/Ctrl + L) and entered the baseline prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a new Rust project for a task management API using Axum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor created a new Rust project structure that includes a &lt;code&gt;Cargo.toml&lt;/code&gt; file with the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"task_manager_api"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8.4"&lt;/span&gt;
&lt;span class="py"&gt;hyper&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.6.0"&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.219"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.142"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.47.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tower-http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.6.6"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also generated a &lt;code&gt;main.rs&lt;/code&gt; file with the API entry point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ok"&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Listening on {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we refined the prompt in the chat panel to flesh out the endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement the complete task API with models, handlers, and main server setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor then updated the &lt;code&gt;main.rs&lt;/code&gt; file with the API models, handlers, and expose the CRUD endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-file editing kept models, routes, and main in sync.&lt;/li&gt;
&lt;li&gt;Inline fixes suggested after compiler errors.&lt;/li&gt;
&lt;li&gt;Excellent for scaffolding large chunks of code fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sometimes hallucinated crate versions.&lt;/li&gt;
&lt;li&gt;Needed a re-prompt to get the right crates and correct some logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for &lt;strong&gt;rapid prototyping and multi-file Rust projects&lt;/strong&gt;, especially for developers comfortable with VS Code-style workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Building with Windsurf&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We created a &lt;code&gt;rust-tasks-windsurf&lt;/code&gt; folder in Windsurf and used the same prompt. Windsurf scaffolded a Rust project but took longer due to its &lt;strong&gt;Cascade&lt;/strong&gt; checks. The upside is that it proposed project structure refinements (splitting &lt;code&gt;handlers.rs&lt;/code&gt; and &lt;code&gt;routes.rs&lt;/code&gt;) and enforced pinned versions in &lt;code&gt;Cargo.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture-aware suggestions (modularized better than Cursor).&lt;/li&gt;
&lt;li&gt;Helpful explanations for async/await and error handling.&lt;/li&gt;
&lt;li&gt;Strong static analysis integration caught subtle mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generation was slower (20-30s per major step).&lt;/li&gt;
&lt;li&gt;Occasionally over-engineered (extra traits not needed for in-memory scope).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for &lt;strong&gt;planning long-lived Rust projects&lt;/strong&gt; where architecture matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Building with VS Code + GitHub Copilot&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We created a new folder &lt;code&gt;rust-tasks-copilot&lt;/code&gt; in VS Code and initialized the project by running the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot helped fill in dependencies, models, and handler boilerplate as we typed. It excelled at incremental completions but required additional guidance to assemble the full API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent line-by-line and function-level completions.&lt;/li&gt;
&lt;li&gt;Copilot Chat explained compiler errors clearly.&lt;/li&gt;
&lt;li&gt;Fastest at suggesting snippets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weak at cross-file consistency (needed manual edits to sync model types).&lt;/li&gt;
&lt;li&gt;Less effective at scaffolding entire APIs in one shot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for &lt;strong&gt;incremental coding and filling gaps&lt;/strong&gt; rather than full project generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Building with Claude Code&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In Claude, we entered the same prompt in the terminal. Claude generated a project scaffold, listed the steps it would take, then produced &lt;code&gt;Cargo.toml&lt;/code&gt; and &lt;code&gt;main.rs&lt;/code&gt;. It explained async concepts and ownership trade-offs in detail while building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear step-by-step reasoning.&lt;/li&gt;
&lt;li&gt;Accurate async handling with axum.&lt;/li&gt;
&lt;li&gt;Strong at explaining lifetimes and error messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower to reach a runnable version (lots of intermediate narration).&lt;/li&gt;
&lt;li&gt;Sometimes verbose in generated comments, cluttering code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for &lt;strong&gt;learning Rust while building&lt;/strong&gt; (doubles as tutor + generator) &lt;strong&gt;and building large-scale projects&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Building with Aider&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For this, we initialized Git and prompted Aider. It scaffolded a Cargo project, generated dependencies, and committed each step with descriptive messages. Its Git-aware workflow was unique: each file edit was atomic, with matching commit messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent Git integration (perfect history for each change).&lt;/li&gt;
&lt;li&gt;Multi-file updates stayed consistent.&lt;/li&gt;
&lt;li&gt;Easy rollback when things went wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required more prompt iteration than Cursor/Windsurf.&lt;/li&gt;
&lt;li&gt;Less context than IDE-native tools (sometimes needed manual cargo fixes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for &lt;strong&gt;collaborative, Git-driven workflows&lt;/strong&gt; in Rust.&lt;/p&gt;

&lt;p&gt;If you're weighing up any of the AI coding tools against each other, here's a quick reference to help decide which fits best in different contexts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Code Quality&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Fast prototyping, full-feature builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;Architecture planning, complex logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Incremental completions, patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Learning and explaining Rust and large-scale projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aider&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;Git-aware, team-oriented dev&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using AI to generate Rust code can be powerful, but even the best tools have their blind spots. IDEs may overlook context rules, terminal tools can feel slower, and both risk introducing subtle bugs if not monitored. The gap between a quick prototype and a maintainable project often comes down to workflow design, rule enforcement, and consistent testing. In the next section, we'll cover best practices for getting the most out of AI coding tools so your Rust projects stay reliable, predictable, and scalable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Getting the Most from AI Coding Tools
&lt;/h2&gt;

&lt;p&gt;AI coding tools can feel like magic, but to get &lt;strong&gt;reliable Rust projects&lt;/strong&gt;, you need &lt;strong&gt;guardrails&lt;/strong&gt;. Start by treating your project like a &lt;strong&gt;product&lt;/strong&gt;. Define the kinds of documents a PM would normally create: &lt;strong&gt;project rules, coding standards, API conventions&lt;/strong&gt;, and so on. A practical approach is to create &lt;code&gt;claude.md&lt;/code&gt; files (or equivalent) for each directory, describing the rules explicitly, and load them into the AI's context. This makes it much easier for any AI tool to generate code within your intended scope, especially in larger projects.&lt;/p&gt;

&lt;p&gt;Next, build &lt;strong&gt;testing discipline&lt;/strong&gt; into your workflow. AI is excellent at TDD, but it will not enforce policy on its own. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run unit tests after every code step.&lt;/li&gt;
&lt;li&gt;Run end-to-end tests after major milestones.&lt;/li&gt;
&lt;li&gt;Avoid mocks and stubs unless absolutely necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practices help reduce &lt;strong&gt;subtle errors&lt;/strong&gt; that AI tools can introduce. Keep in mind that IDE-based tools such as &lt;strong&gt;Cursor, Copilot, and Windsurf&lt;/strong&gt; sometimes ignore context-loaded rule files. CLI-based tools combined with dotfiles tend to respect them more consistently. That is why CLI workflows can be especially valuable for large or complex Rust projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and permissions&lt;/strong&gt; are another important consideration. CLI tools usually give you fine-grained control. You can grant permissions per command, or opt in to full access with flags like &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; in Claude CLI. IDEs, by contrast, often require broader access and provide fewer safeguards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; is also a factor. CLI tools typically consume &lt;strong&gt;tokens&lt;/strong&gt; directly from your AI subscription, which can add up quickly on big projects. IDEs such as &lt;strong&gt;Cursor or Windsurf&lt;/strong&gt; let you switch between models, but the most capable ones often sit behind pay-as-you-go tiers that can exceed $1,000 for heavy usage. Anthropic Max, for example, offers a predictable monthly price with a token reset system. This makes budgeting easier for long-term Rust projects.&lt;/p&gt;

&lt;p&gt;In short, enforce guardrails, define clear rules, and think like a PM. With that mindset, both IDE and CLI AI environments will work more predictably, generate higher-quality Rust code, and integrate smoothly into your deployment pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Deployed the Rust Applications
&lt;/h2&gt;

&lt;p&gt;Each tool got us to a working Rust API, but in different ways. Some needed a bit of re-prompting, while others were more direct. Instead of setting up separate Dockerfiles, CI pipelines, or custom cloud configs for each tool, we kept things simple with a single deployment workflow powered by Shuttle's &lt;a href="https://docs.shuttle.dev/integrations/mcp-server" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP). Here's how you can do the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set up Shuttle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;a href="https://console.shuttle.dev/signup" rel="noopener noreferrer"&gt;free Shuttle account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://docs.shuttle.dev/getting-started/installation" rel="noopener noreferrer"&gt;Shuttle CLI&lt;/a&gt;, which lets you deploy straight from your machine.&lt;/li&gt;
&lt;li&gt;Configure the CLI by running: 
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  Shuttle login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open a browser where you can log in with your credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect your AI Coding Tool to Shuttle MCP:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the Shuttle MCP server to your tool of choice. For example, in Windsurf:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Settings → Windsurf Settings → Manage MCPs → View raw config.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste the server configuration below into the config file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Shuttle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shuttle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Check &lt;a href="https://docs.shuttle.dev/integrations/mcp-server" rel="noopener noreferrer"&gt;Shuttle's documentation&lt;/a&gt; for the full list of MCP configurations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prompt the Tool to Deploy:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, ask your AI coding tool to deploy the API through the Shuttle MCP server. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deploy the API to Shuttle.

Use the Shuttle MCP server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;As a rule of thumb, always include the phrase _&lt;code&gt;Use the Shuttle MCP server.&lt;/code&gt;&lt;/em&gt; when deploying through the Shuttle MCP server so it can reference the documentation during deployment._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Let the Tool Update your Project:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you run the prompt, the tool will update your project for deployment on Shuttle. This includes updating &lt;code&gt;Cargo.toml&lt;/code&gt; to add the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;shuttle-axum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.56.0"&lt;/span&gt;
&lt;span class="py"&gt;shuttle-runtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.56.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will also update the &lt;code&gt;main&lt;/code&gt; function to use the Shuttle runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;AppState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tasks/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete_task&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/health"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CorsLayer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;permissive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;View your Deployment:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, open your &lt;a href="https://console.shuttle.dev/" rel="noopener noreferrer"&gt;Shuttle console&lt;/a&gt; to grab your API URL and other deployment artifacts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgj555445w7p22bll1r9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgj555445w7p22bll1r9l.png" alt="Shuttle Console" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond Deployment with Shuttle
&lt;/h3&gt;

&lt;p&gt;Shuttle goes further than just deployment. It provides tools and resources that make day-to-day development smoother and more productive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ready-to-deploy resources&lt;/strong&gt; like databases and built-in secrets for managing environment variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-effort prototyping and framework support&lt;/strong&gt;, so you can spin up projects quickly with Axum, Actix, Rocket, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community-driven development and support&lt;/strong&gt;, with an active open-source community, Discord, and programs to support your workflow. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast redeploys and local iteration&lt;/strong&gt;, making it easy to test changes without waiting on long build times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt; powered by Rust macros, which means your infrastructure lives directly inside your Rust code instead of separate config files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The AI coding landscape for Rust is more powerful and flexible than ever. Whether you prefer the visual depth of IDE-based tools or the speed and focus of terminal applications, there's a tool that matches your style.&lt;/p&gt;

&lt;p&gt;One thing that never changes is the importance of Rust's core principles. The best AI tool for coding is the one that helps you write idiomatic, safe, and performant Rust while fitting naturally into your workflow.&lt;/p&gt;

&lt;p&gt;Whatever tool you choose, Shuttle makes building and deploying your applications effortless. Ready to see it in action? Run this command to get started with a simple Axum web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rust</category>
      <category>ai</category>
      <category>codingtools</category>
      <category>development</category>
    </item>
    <item>
      <title>How to Build and Deploy an SSE MCP Server with OAuth in Rust</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Mon, 01 Sep 2025 16:19:54 +0000</pubDate>
      <link>https://dev.to/shuttle/how-to-build-and-deploy-an-sse-mcp-server-with-oauth-in-rust-3h5k</link>
      <guid>https://dev.to/shuttle/how-to-build-and-deploy-an-sse-mcp-server-with-oauth-in-rust-3h5k</guid>
      <description>&lt;p&gt;AI agents have become integral to modern development workflows, transforming how we build and maintain software. While these tools are already powerful, they reach their full potential when enhanced with &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; servers that extend their capabilities through specialized tools and integrations.&lt;/p&gt;

&lt;p&gt;For developers running hosted applications or platforms, MCP servers offer a unique opportunity to provide users with natural language interfaces to your services. Consider a project management platform: instead of navigating through multiple screens, users could authorize an MCP server and then create tasks, update project statuses, or generate reports using simple conversational commands through their preferred AI client.&lt;/p&gt;

&lt;p&gt;Building secure MCP servers requires adherence to two critical standards: the Model Context Protocol specification and &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13" rel="noopener noreferrer"&gt;OAuth 2.1&lt;/a&gt;. When implemented correctly, your MCP server becomes universally compatible with any MCP-enabled AI tool—whether users prefer &lt;a href="https://cursor.sh/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, &lt;a href="https://claude.ai/desktop" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt;, Windsurf, or other platforms.&lt;/p&gt;

&lt;p&gt;This tutorial provides a comprehensive guide to OAuth authentication patterns and walks through building a production-ready MCP server. We'll implement secure authentication flows that allow AI agents to safely interact with your hosted services, then deploy the complete solution using &lt;a href="https://shuttle.dev/" rel="noopener noreferrer"&gt;Shuttle's&lt;/a&gt; streamlined cloud deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Transport Types
&lt;/h2&gt;

&lt;p&gt;MCP servers use two transport mechanisms: &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio" rel="noopener noreferrer"&gt;&lt;strong&gt;STDIO&lt;/strong&gt;&lt;/a&gt; (standard input/output) and &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#http-with-sse" rel="noopener noreferrer"&gt;&lt;strong&gt;SSE&lt;/strong&gt;&lt;/a&gt; (Server-Sent Events). The stdio transport runs as a local subprocess and communicates through standard input/output streams, which we covered in detail in our &lt;a href="https://www.shuttle.dev/blog/2025/07/18/how-to-build-a-stdio-mcp-server-in-rust" rel="noopener noreferrer"&gt;comprehensive guide to building stdio MCP servers in Rust&lt;/a&gt;. SSE transport type servers, on the other hand, use HTTP-based communication with Server-Sent Events for real-time messaging.&lt;/p&gt;

&lt;p&gt;SSE servers operate as cloud-hosted services, making them accessible from anywhere with proper network connectivity. Users don't need to install anything locally—they can access your MCP server through a simple URL. SSE transport type servers integrate directly with your existing backend infrastructure and support robust authentication mechanisms like OAuth 2, enabling secure access control and user management.&lt;/p&gt;

&lt;p&gt;This tutorial focuses on building an &lt;strong&gt;SSE MCP server with OAuth&lt;/strong&gt;—the cloud-based approach that connects to your backend application and provides authenticated access to your services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding OAuth 2 for MCP Servers
&lt;/h2&gt;

&lt;p&gt;In order for users to authorize their MCP clients and AI agents to perform actions on their behalf, MCP servers must implement OAuth 2 specifications. In this section, we'll dive deep into the OAuth 2 requirements, what MCP clients expect, and what the flow looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth Flow
&lt;/h3&gt;

&lt;p&gt;The OAuth flow consists of five key phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discovery Phase&lt;/strong&gt;: Client discovers authorization server metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration Phase&lt;/strong&gt;: Client registers itself with the authorization server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization Phase&lt;/strong&gt;: User consent and authorization code generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Exchange&lt;/strong&gt;: MCP client exchanges the authorization code for an access token and refresh token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticated Access&lt;/strong&gt;: Using access tokens to connect to the protected MCP SSE endpoints&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  OAuth 2 Flow Deep Dive
&lt;/h3&gt;

&lt;p&gt;OAuth might seem intimidating at first glance, but it's actually a straightforward flow once you understand the components. Let's dive into the OAuth flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Discovery Phase (Metadata Endpoint)
&lt;/h3&gt;

&lt;p&gt;For MCP clients to discover your authorization server endpoints, we need to create a well-known route that MCP clients will always query before starting the authentication flow.&lt;/p&gt;

&lt;p&gt;MCP clients query the &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt; endpoint to retrieve the &lt;strong&gt;Authorization Server Metadata&lt;/strong&gt; (RFC 8414). This JSON document lists your OAuth 2.0 endpoints, grant types, and scopes. Implementing it is mandatory. Without it, MCP clients can't authenticate with your server.&lt;/p&gt;

&lt;p&gt;The metadata endpoint provides important information to the MCP client, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registration endpoint&lt;/strong&gt;: The route where MCP clients can register themselves with your authorization server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization endpoint&lt;/strong&gt;: The MCP client will redirect the user to this route so that the user can manually review the authorization request and approve or reject it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token endpoint&lt;/strong&gt;: After the user approves the authorization request, the MCP client will exchange the authorization code (generated by the server in the previous step) for an access token, this token will be used for authenticated requests from the MCP client to the MCP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Registration Phase
&lt;/h3&gt;

&lt;p&gt;During the initial connection setup, the MCP client first discovers the authorization server endpoints via the metadata endpoint, then makes a request to the &lt;strong&gt;registration endpoint&lt;/strong&gt;, providing relevant information about itself. The server saves this information in the database and responds with a &lt;strong&gt;client ID&lt;/strong&gt; and &lt;strong&gt;client secret&lt;/strong&gt; for the MCP client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Authorization Phase (User Consent)
&lt;/h3&gt;

&lt;p&gt;The first time an MCP server is added to an MCP client, the server cannot be used until the user authenticates with it, here is an example of how the Notion MCP server looks in Cursor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5mb27xtx3yhfput4j2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5mb27xtx3yhfput4j2t.png" alt="Notion MCP server in Cursor" width="668" height="73"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user clicks on the &lt;strong&gt;Login&lt;/strong&gt; button through their MCP client, the MCP client will redirect the user to the &lt;strong&gt;authorization endpoint&lt;/strong&gt; that was provided using the &lt;strong&gt;metadata endpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The user can then authenticate using their existing account and approve the authorization request. After successful authentication, the server redirects the user back to the MCP client with an &lt;strong&gt;authorization code&lt;/strong&gt; in the URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6ur5yr1f7on7o7pu1g7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6ur5yr1f7on7o7pu1g7.png" alt="Notion authorize page" width="519" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Token Exchange
&lt;/h3&gt;

&lt;p&gt;After the user approves the authorization request, the MCP client immediately uses the &lt;strong&gt;authorization code&lt;/strong&gt; to make a request to the &lt;strong&gt;token endpoint&lt;/strong&gt;, exchanging the code for an &lt;strong&gt;access token&lt;/strong&gt; and &lt;strong&gt;refresh token&lt;/strong&gt;. The access token authenticates the MCP client to the MCP server, while the refresh token renews the access token when it expires.&lt;/p&gt;

&lt;p&gt;The MCP client will now be able to make authenticated requests and ready for use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Authenticated Access
&lt;/h3&gt;

&lt;p&gt;The MCP client with the use of the access token will now be able to connect to the MCP server and the MCP server will have the ability to identify the MCP client and the user that authorized it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refreshing the Access Token
&lt;/h3&gt;

&lt;p&gt;The refresh token is used to renew the access token when it expires. The token endpoint must be designed to support both the refresh token grant type and the authorization code grant type, which we'll implement later in the tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and Deploying an SSE MCP Server in Rust
&lt;/h2&gt;

&lt;p&gt;Now that we have a solid understanding of MCP servers and OAuth integration, we'll build a production-ready SSE MCP server that fully complies with both the MCP protocol and OAuth 2 specifications.&lt;/p&gt;

&lt;p&gt;After building and testing the MCP server, we'll then deploy it to the cloud using &lt;strong&gt;Shuttle&lt;/strong&gt; with just a single command.&lt;/p&gt;

&lt;p&gt;You can find the complete code for this project in the &lt;a href="https://github.com/shuttle-hq/shuttle-examples/tree/main/mcp/mcp-sse-oauth" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To follow along, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intermediate Rust familiarity (async/await, Axum, traits)&lt;/li&gt;
&lt;li&gt;Basic OAuth concepts (auth codes, tokens, etc.)&lt;/li&gt;
&lt;li&gt;Experience with PostgreSQL and SQLx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial focuses on key patterns. For the complete, unabridged code, please refer to the accompanying &lt;a href="https://github.com/shuttle-hq/shuttle-examples/tree/main/mcp/mcp-sse-oauth" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the MCP Inspector
&lt;/h3&gt;

&lt;p&gt;The MCP inspector is a tool provided by the MCP team to help you test and debug your MCP server. You need &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and npm installed on your machine to run it. Execute the following command to install and run the MCP inspector in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9xuexy6m6h4oezgmajw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9xuexy6m6h4oezgmajw.png" alt="Running the MCP inspector" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will automatically open the inspector in your default browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes26q7uixn2lujjo1jvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes26q7uixn2lujjo1jvp.png" alt="MCP inspector dashboard" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll use the MCP inspector to test our OAuth flow at each stage of the tutorial&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ged84ovtk1g717n9vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ged84ovtk1g717n9vc.png" alt="MCP inspector open Auth settings" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Open Auth Settings" button which will open the auth settings page that tests the OAuth steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8uzayqczljzc5j82lxxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8uzayqczljzc5j82lxxc.png" alt="MCP inspector auth flow" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we'll implement the metadata endpoint. We'll build our server using the official &lt;code&gt;rmcp&lt;/code&gt; crate and Axum, which are compatible out of the box.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;rmcp&lt;/code&gt; crate to your &lt;code&gt;Cargo.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;rmcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"transport-sse-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature flags are self-explanatory: we need the &lt;code&gt;server&lt;/code&gt; flag to build an MCP server (not a client), &lt;code&gt;transport-sse-server&lt;/code&gt; for SSE transport functionality, and &lt;code&gt;auth&lt;/code&gt; for OAuth server utilities.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;main.rs&lt;/code&gt; file, we have &lt;strong&gt;Shuttle&lt;/strong&gt; boilerplate code that provisions a &lt;a href="https://docs.shuttle.dev/resources/shuttle-shared-db" rel="noopener noreferrer"&gt;PostgreSQL database&lt;/a&gt; in production and implements the Shuttle &lt;a href="https://docs.rs/shuttle-service/0.56.0/shuttle_service/trait.Service.html" rel="noopener noreferrer"&gt;Service Trait&lt;/a&gt; to get the socket address and run the MCP server, The &lt;code&gt;Service&lt;/code&gt; trait ensures that the code will work in both development and production environments. We'll write the rest of the code in the &lt;code&gt;init.rs&lt;/code&gt; file as the entry point for our backend server which will serve the authentication APIs and the MCP server endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;McpSseService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[shuttle_runtime::async_trait]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;McpSseService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;init&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.secrets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_shared_db::Postgres(&lt;/span&gt;
        &lt;span class="nd"&gt;local_uri&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postgres://postgres:password@localhost:5432/mcp-sse-auth"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_runtime::Secrets]&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;McpSseService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;McpSseService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;shuttle_shared_db::Postgres&lt;/code&gt; macro to provision a PostgreSQL database in production and &lt;code&gt;local_uri&lt;/code&gt; to connect to a local database for development purposes only. The &lt;code&gt;shuttle_runtime::Secrets&lt;/code&gt; macro is used to access secrets from the &lt;code&gt;Secrets.toml&lt;/code&gt; file for development as well as deployment, which we'll create in the next step.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;init()&lt;/code&gt; function contains all the &lt;code&gt;rmcp&lt;/code&gt; boilerplate required to spin up the SSE transport MCP server. You can view the &lt;a href="https://github.com/shuttle-hq/shuttle-examples/blob/main/mcp/mcp-sse-oauth/src/init.rs" rel="noopener noreferrer"&gt;complete implementation here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Secrets File
&lt;/h3&gt;

&lt;p&gt;Shuttle uses &lt;code&gt;Secrets.toml&lt;/code&gt; files to store project secrets. We'll create two files: &lt;code&gt;Secrets.toml&lt;/code&gt; for production and &lt;code&gt;Secrets.dev.toml&lt;/code&gt; for development in the project root. We'll use the &lt;code&gt;openssl&lt;/code&gt; command to generate a random JWT secret key. Run the following command to generate a secure key:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security Note: Never commit your Secrets.toml files to version control. Add them to your .gitignore file to prevent accidental exposure of sensitive information.&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 32
&lt;span class="c"&gt;# Output: sQGnE/aD76G2TAJA6HqJk9shkmYwsmwZ3b+sJlQWBVE=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your &lt;code&gt;Secrets.dev.toml&lt;/code&gt; file, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;BASE_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8000"&lt;/span&gt;
&lt;span class="py"&gt;JWT_SECRET&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"sQGnE/aD76G2TAJA6HqJk9shkmYwsmwZ3b+sJlQWBVE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;" # Replace with your own JWT secret key&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get your production URL by navigating to the &lt;a href="https://console.shuttle.dev/" rel="noopener noreferrer"&gt;Shuttle Console&lt;/a&gt; and bootstrap a new project. The URL will be displayed in the console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndj0h7lxyvn62d86q4fe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndj0h7lxyvn62d86q4fe.png" alt="Shuttle console project URL" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generate another random JWT secret key and update your &lt;code&gt;Secrets.toml&lt;/code&gt; file as well, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;BASE_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://your-project.shuttle.app"&lt;/span&gt; &lt;span class="c"&gt;# Add your production URL here&lt;/span&gt;
&lt;span class="py"&gt;JWT_SECRET&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"FgaRCPwUd86iRwQsAm9faAky59ghk0c3bhSijz9wbAM&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;" # Replace with your own JWT secret key&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the Development Server
&lt;/h3&gt;

&lt;p&gt;After &lt;strong&gt;Shuttle&lt;/strong&gt; and &lt;strong&gt;rmcp&lt;/strong&gt; boilerplate is set up, we can run the development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle run &lt;span class="nt"&gt;--secrets&lt;/span&gt; Secrets.dev.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an auto-reload development server, you can use &lt;a href="https://crates.io/crates/cargo-watch" rel="noopener noreferrer"&gt;cargo-watch&lt;/a&gt; to run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo watch &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"shuttle run --secrets Secrets.dev.toml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically restarts the server when you make code changes. You'll need to install &lt;a href="https://crates.io/crates/cargo-watch" rel="noopener noreferrer"&gt;cargo-watch&lt;/a&gt; first by running &lt;code&gt;cargo install cargo-watch --locked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmejw4q85o847hdvl1da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmejw4q85o847hdvl1da.png" alt="Shuttle Development server running" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Excellent! Our MCP server is now running on &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt; and we can test it using the MCP inspector.&lt;/p&gt;

&lt;p&gt;Update your MCP inspector to use the correct MCP server URL, in our case it's &lt;code&gt;http://127.0.0.1:8000/mcp/sse&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8rkfg5sg4q2xb7c5qwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8rkfg5sg4q2xb7c5qwv.png" alt="MCP inspector server URL" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason our MCP server is running on &lt;code&gt;http://127.0.0.1:8000/mcp/sse&lt;/code&gt; is because how we configured the the rmcp boilerplate code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sse_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SseServerConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sse_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"/mcp/sse"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;post_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"/mcp/message"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;sse_keep_alive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this configuration, we've specified the MCP server to run on &lt;code&gt;http://127.0.0.1:8000/mcp/sse&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Metadata Endpoint
&lt;/h3&gt;

&lt;p&gt;Clients can connect to the server but can't authenticate yet. To fix this, we'll start with the discovery phase by implementing the &lt;strong&gt;&lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt;&lt;/strong&gt; route. Clients use this endpoint to fetch auth metadata, and since &lt;code&gt;rmcp&lt;/code&gt; is compatible with Axum, we can easily create this route to return a JSON response.&lt;/p&gt;

&lt;p&gt;According to the OAuth 2 specification, the metadata is expected to include the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;issuer&lt;/code&gt;: The base URL of the authorization server. e.g. &lt;code&gt;https://my-app.shuttle.app&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;registration_endpoint&lt;/code&gt;: MCP clients can register themselves with the authorization server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;authorization_endpoint&lt;/code&gt;: MCP clients will redirect the user to this route so that the user can manually review the authorization request and approve or reject it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;token_endpoint&lt;/code&gt;: MCP clients will use this route to exchange the authorization code for an access token and refresh token, this route will be re-used for refreshing the access token when it expires.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scopes_supported&lt;/code&gt;: The scopes supported by the authorization server. e.g. &lt;code&gt;profile&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;additional_fields&lt;/code&gt;: Additional fields that can be used to customize the authorization server (according to the RFC 8414).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwks_uri&lt;/code&gt;: The URL of the &lt;a href="https://datatracker.ietf.org/doc/html/rfc7517" rel="noopener noreferrer"&gt;JSON Web Key Set (JWKS)&lt;/a&gt; endpoint, this is optional and can be omitted if not needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's implement the metadata endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;oauth_authorization_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
        &lt;span class="py"&gt;.secrets&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BASE_URL secret not found"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;additional_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;additional_fields&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"response_types_supported"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())]),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;additional_fields&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"code_challenge_methods_supported"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"S256"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())]),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AuthorizationMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="n"&gt;registration_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{base_url}/oauth/register"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;authorization_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{base_url}/oauth/authorize"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;token_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{base_url}/oauth/token"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="n"&gt;scopes_supported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"profile"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()]),&lt;/span&gt;
        &lt;span class="n"&gt;jwks_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;additional_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've added the following fields to advertise our support for the Authorization Code grant with PKCE (S256), in compliance with RFC 8414.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;additional_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;additional_fields&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"response_types_supported"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())]),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;additional_fields&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"code_challenge_methods_supported"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"S256"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())]),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it with the MCP inspector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fnvd36uufpcaqthge2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fnvd36uufpcaqthge2u.png" alt="MCP inspector metadata endpoint success" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Excellent! The metadata endpoint is working. Now let's implement the registration endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Registration Route
&lt;/h3&gt;

&lt;p&gt;After using the metadata endpoint, MCP clients register themselves by sending a request to the Register Route. The server saves the client's information to the database and responds with a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we need a helper function to generate secure client secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;generate_client_secret&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nd"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"{byte:02x}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the registration handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ClientRegistrationRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;client_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;redirect_uris&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;client_registration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ClientRegistrationRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate redirect URIs&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.redirect_uris&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"invalid_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"redirect_uris is required and must not be empty"&lt;/span&gt;
        &lt;span class="p"&gt;})))&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate client credentials&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_client_secret&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
        &lt;span class="py"&gt;.client_name&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="s"&gt;"MCP Client"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;issued_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;chrono&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.timestamp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;chrono&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nn"&gt;chrono&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Store client in database&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;query_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;r#"
        INSERT INTO mcp_clients (client_id, client_secret, client_name, redirect_uris, client_secret_expires_at)
        VALUES ($1, $2, $3, $4, $5)
        "#&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.redirect_uris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expires_at&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;query_result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClientRegistrationResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;client_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;redirect_uris&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.redirect_uris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mcp"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;client_id_issued_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issued_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;client_secret_expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="nf"&gt;.timestamp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to register client: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;json!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"server_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Failed to register client"&lt;/span&gt;
                &lt;span class="p"&gt;})),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test our registration endpoint to make sure it's working correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29bl8m06sid21ydi88xw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29bl8m06sid21ydi88xw.png" alt="MCP inspector client registration endpoint success" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The registration endpoint is working. Now let's implement the authorization endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Authorization Endpoint
&lt;/h3&gt;

&lt;p&gt;The authorization endpoint shows the user a consent screen with the requested scopes. When the user clicks "Allow," the server does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates an authorization code.&lt;/li&gt;
&lt;li&gt;Saves the code to the database.&lt;/li&gt;
&lt;li&gt;Redirects the user back to the client with the code.
Finally, the client exchanges this authorization code for an access token and a refresh token.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: In a real-world app, you would require users to be logged in before they see the consent screen, the &lt;code&gt;authorize_get&lt;/code&gt; and &lt;code&gt;authorized_post&lt;/code&gt; routes must be protected by your applications authentication mechanism i.e. a middleware.&lt;/p&gt;

&lt;p&gt;For simplicity in this tutorial, we're skipping that login step. We'll simply use the &lt;code&gt;client_id&lt;/code&gt; as the user identifier to keep things simple&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the frontend, we'll use the template engine &lt;a href="https://github.com/askama-rs/askama" rel="noopener noreferrer"&gt;Askama&lt;/a&gt; to render the consent UI. We've already created an HTML template that you can find in the &lt;a href="https://github.com/shuttle-hq/shuttle-examples/tree/main/mcp/mcp-sse-oauth/templates" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. Using the &lt;code&gt;askama&lt;/code&gt; crate, we can create a struct for template rendering and then render the template using the &lt;code&gt;render&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Template)]&lt;/span&gt;
&lt;span class="nd"&gt;#[template(path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"authorize.html"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AuthorizeTemplate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to use the derive macro &lt;code&gt;#[derive(Template)]&lt;/code&gt; to register the template and the &lt;code&gt;#[template(path = "authorize.html")]&lt;/code&gt; attribute to specify the template file path.&lt;/p&gt;

&lt;p&gt;After that, we can send the rendered template as an HTML response using the &lt;code&gt;axum::response::Html&lt;/code&gt; type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;authorize_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthorizeRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate required parameters&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.response_type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"code"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unsupported response type"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Look up client in database&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"SELECT client_name FROM mcp_clients WHERE client_id = $1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.client_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.fetch_optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;client_result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid client"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Database error: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Internal server error"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.scope&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="s"&gt;"profile email"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AuthorizeTemplate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="py"&gt;.client_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;code_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="nf"&gt;.render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Template render error: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Template error"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's handle the authorization click. When a user approves the request, our server will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate an &lt;strong&gt;authorization code&lt;/strong&gt; and save it to the database.&lt;/li&gt;
&lt;li&gt;Redirect the user back to the client using the provided &lt;code&gt;redirect_uri&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;authorize_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthorizeForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"deny"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}?error=access_denied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.redirect_uri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;state={state}"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate authorization code&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_authorization_code&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10 minute expiration&lt;/span&gt;

    &lt;span class="c1"&gt;// Store authorization code in database&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;store_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;r#"
        INSERT INTO authorization_codes (code, client_id, redirect_uri, code_challenge, expires_at)
        VALUES ($1, $2, $3, $4, $5)
        "#&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auth_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.code_challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expires_at&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;store_result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}?code={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.redirect_uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth_code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;state={state}"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nn"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to store authorization code: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}?error=server_error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.redirect_uri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;state={state}"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nn"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;redirect_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test our authorization endpoint with the MCP inspector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsipj05u6mso0ta88e6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsipj05u6mso0ta88e6i.png" alt="MCP inspector authorization endpoint success" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The inspector displays the backend authorization URL we just created, which you can open in a browser to see the consent UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61o1koebxzkki9u41ovy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61o1koebxzkki9u41ovy.png" alt="Authorization endpoint consent screen" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Authorize"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtcbzkza0xu2deawyoi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtcbzkza0xu2deawyoi8.png" alt="MCP inspector displaying authorization code" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The inspector now &lt;strong&gt;displays&lt;/strong&gt; the &lt;strong&gt;authorization code&lt;/strong&gt; from the backend, which we'll use in the next step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9hlvcqw0udzv7b14s6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9hlvcqw0udzv7b14s6.png" alt="MCP inspector authorization success" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All steps done for this phase, let's move on to the next step which is the token endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Token Exchange
&lt;/h3&gt;

&lt;p&gt;So far, the user has authorized the client and been redirected back with an authorization code.&lt;/p&gt;

&lt;p&gt;Next, the client must exchange that authorization code for an actual access token. It does this by making a POST request to our token endpoint.&lt;/p&gt;

&lt;p&gt;To be fully compliant with OAuth 2.0, this single endpoint needs to handle two different grant types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exchanging the initial authorization code for tokens.&lt;/li&gt;
&lt;li&gt;Exchanging a refresh token for a new access token later on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To handle this, we'll build two main functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;handle_authorization_code_grant&lt;/code&gt;: This function will validate the incoming authorization code and PKCE verifier from the database. If they are valid, it creates the first access token and refresh token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handle_refresh_token_grant&lt;/code&gt;: This function validates an existing refresh token. If it's valid, it issues a new access token and implements token rotation. This is a security best practice where a new refresh token is also issued, invalidating the old one.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;token_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TokenRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.grant_type&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"authorization_code"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle_authorization_code_grant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"refresh_token"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle_refresh_token_grant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ErrorResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"unsupported_grant_type"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;error_description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Only authorization_code and refresh_token grant types are supported"&lt;/span&gt;
                        &lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/shuttle-hq/shuttle-examples/blob/main/mcp/mcp-sse-oauth/src/auth/token.rs" rel="noopener noreferrer"&gt;full implementation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's test our token endpoint with the MCP inspector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoknfuq8qngw8yugdowy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoknfuq8qngw8yugdowy.png" alt="MCP inspector token endpoint success" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! All steps done for the OAuth 2.0 authentication, and we're ready to move on to the next step which is creating a middleware to protect the MCP endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing JWT Authentication Middleware
&lt;/h3&gt;

&lt;p&gt;To secure our MCP service, we need to validate the JWT on every request made by a client. We'll use a middleware to handle this, ensuring only authenticated clients can access our MCP tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT Claims Structure
&lt;/h3&gt;

&lt;p&gt;The JWT we generated earlier contains the &lt;code&gt;client_id&lt;/code&gt;. By decoding this token on every incoming request, we can extract the &lt;code&gt;client_id&lt;/code&gt; to identify which client is making the call.&lt;/p&gt;

&lt;p&gt;First, we'll define a struct that mirrors the &lt;strong&gt;JWT claims&lt;/strong&gt; we generated at the token endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Claims&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// client_id&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// issued at&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// expires at&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// granted scopes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Validation Middleware
&lt;/h3&gt;

&lt;p&gt;The middleware extracts and validates JWT tokens from the Authorization header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;validate_token_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Extract the access token from the Authorization header&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="nf"&gt;.headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;header_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid Authorization header encoding"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header_str&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization header missing Bearer prefix"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Missing Authorization header"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Get JWT secret from configuration&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;jwt_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
        &lt;span class="py"&gt;.secrets&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JWT_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JWT_SECRET secret not found"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate JWT token&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DecodingKey&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt_secret&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Validation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Check if token is expired (JWT validation already handles this, but being explicit)&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;chrono&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.timestamp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims.exp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token has expired"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Verify the client still exists in database&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"SELECT client_id FROM mcp_clients WHERE client_id = $1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims.sub&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.fetch_optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;client_exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Add client_id to request extensions for downstream handlers&lt;/span&gt;
                    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="nf"&gt;.extensions_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims.sub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Client no longer exists: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims.sub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Database error validating client: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token validation failed: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="nf"&gt;.into_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the middleware, we extract the Bearer token and verify it's valid and not expired. Then we extract the &lt;code&gt;client_id&lt;/code&gt; from it and verify the client exists in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="nf"&gt;.extensions_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_data&lt;/span&gt;&lt;span class="py"&gt;.claims.sub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code snippet is a crucial part of the middleware, we add the &lt;code&gt;client_id&lt;/code&gt; to the &lt;a href="https://docs.rs/axum/latest/axum/middleware/index.html#passing-state-from-middleware-to-handlers" rel="noopener noreferrer"&gt;request extensions&lt;/a&gt; so it can be used by the next handlers and MCP tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the service struct
&lt;/h3&gt;

&lt;p&gt;We will define our entire service within a central &lt;code&gt;struct&lt;/code&gt;. This approach allows us to implement the necessary &lt;code&gt;rmcp&lt;/code&gt; traits and macros, which will contain all of our MCP logic like &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;tools&lt;/a&gt;, &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/resources" rel="noopener noreferrer"&gt;resources&lt;/a&gt;, &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/prompts" rel="noopener noreferrer"&gt;prompts&lt;/a&gt;, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TodoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;db_pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;client_id&lt;/code&gt; here is key because it will be used to identify the client that made the request in every tool call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Handler Implementation
&lt;/h3&gt;

&lt;p&gt;Next, we'll implement the &lt;code&gt;ServerHandler&lt;/code&gt; trait. This trait handles core protocol logic and ensures our server is compliant without having to manage the low-level details.&lt;br&gt;
It has many methods, but most of them are optional.&lt;/p&gt;

&lt;p&gt;To get our server running, we only need to implement the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;initialize&lt;/code&gt;: Handles the initial setup when a client connects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_info&lt;/code&gt;: Provides essential metadata about our service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll ignore the other optional methods like &lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;list_prompts&lt;/code&gt;, and &lt;code&gt;list_resources&lt;/code&gt; for the time being.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementing &lt;code&gt;get_info&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;get_info&lt;/code&gt; method is used to provide metadata about our MCP service, MCP clients will fetch this metadata and the AI model will understand what this MCP server is used for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool_handler]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ServerHandler&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;TodoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;protocol_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;ProtocolVersion&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;V_2024_11_05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;ServerCapabilities&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.enable_prompts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.enable_resources&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.enable_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;server_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Implementation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_build_env&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This server provides todo management tools. You can create, read, update, and delete todos. Each todo has an id, title, and completion status."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing &lt;code&gt;initialize&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; method executes on initial MCP client-server connection. Running post-JWT middleware, it extracts the &lt;code&gt;client_id&lt;/code&gt; from middleware extensions and caches it for subsequent operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool_handler]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ServerHandler&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;TodoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InitializeRequestParam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RoleServer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InitializeResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_request_part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="py"&gt;.extensions.get&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;http&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_request_part&lt;/span&gt;&lt;span class="py"&gt;.extensions.get&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client_id&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;warn!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No client_id found in HTTP request extensions"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_info&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication for persistent SSE connections works differently than for standard HTTP requests. We validate the JWT only once when the connection is first established via the &lt;code&gt;initialize&lt;/code&gt; method. All subsequent messages on that same connection are then considered authenticated&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; method's signature takes &lt;code&gt;&amp;amp;self&lt;/code&gt; (an immutable reference), not &lt;code&gt;&amp;amp;mut self&lt;/code&gt;. This presents a challenge: we're not allowed to directly change our service's state (like setting the &lt;code&gt;client_id&lt;/code&gt;) from within the method.&lt;/p&gt;

&lt;p&gt;To work around this restriction, we use a pattern called interior mutability. We wrap our &lt;code&gt;client_id&lt;/code&gt; in two special types that allow for safe modification even from an immutable context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Arc&lt;/code&gt;: Allows the data to be safely owned and shared across multiple asynchronous tasks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokio::sync::Mutex&lt;/code&gt;: Acts as a lock that ensures only one task can access and change the data at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination lets us safely mutate the value from within a method that only has a &lt;code&gt;&amp;amp;self&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the MCP Todo Service
&lt;/h3&gt;

&lt;p&gt;Let's do a quick recap of how the connection is being handled so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MCP client requests OAuth metadata.&lt;/li&gt;
&lt;li&gt;User clicks the login button presented by the MCP client.&lt;/li&gt;
&lt;li&gt;User is redirected to the authorization endpoint.&lt;/li&gt;
&lt;li&gt;User &lt;strong&gt;Authorizes&lt;/strong&gt; the MCP client.&lt;/li&gt;
&lt;li&gt;Server redirects the user back to the MCP client with the authorization code.&lt;/li&gt;
&lt;li&gt;MCP client exchanges the authorization code for the access token and refresh token.&lt;/li&gt;
&lt;li&gt;MCP client establishes a persistent connection to the MCP endpoint (i.e &lt;code&gt;/mcp/sse&lt;/code&gt;) using the access token as a Bearer token in the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;The JWT middleware validates the access token and extract the &lt;code&gt;client_id&lt;/code&gt; from it.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;TodoService&lt;/code&gt; has now access to the &lt;code&gt;client_id&lt;/code&gt; and can use it to perform actions on behalf of the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementing the MCP tools
&lt;/h3&gt;

&lt;p&gt;MCP tools work like regular functions: they have names, input parameters, and outputs. When AI agents call these tools, they pass the required parameters and receive results back through the JSON-RPC 2.0 protocol.&lt;br&gt;
The &lt;code&gt;rmcp&lt;/code&gt; crate simplifies schema generation by using &lt;code&gt;schemars&lt;/code&gt; to create JSON-RPC 2.0 compatible schemas automatically. With the derive macro, we can implement the &lt;code&gt;JsonSchema&lt;/code&gt; trait and generate tool schemas without manual work.&lt;br&gt;
For our todo creation tool, we need just a &lt;code&gt;title&lt;/code&gt; field and an optional &lt;code&gt;completed&lt;/code&gt; field. The tool returns a success message confirming the operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;rmcp::schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CreateTodoInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing MCP Tools
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;rmcp&lt;/code&gt; library provides powerful macros to automatically generate MCP tools. The &lt;code&gt;#[tool_router]&lt;/code&gt; macro creates a router for all our tools, while &lt;code&gt;#[tool]&lt;/code&gt; generates individual tool handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool_router]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;TodoService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;db_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[tool(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Create a new todo item"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreateTodoInput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract client_id for user-specific data operations&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.client_id&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;McpError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Client not authenticated"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CreateTodoRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="py"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="py"&gt;.completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Pass client_id to database operations for user isolation&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;db&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;todo_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_string_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nn"&gt;McpError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Serialization error: {e}"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Todo created successfully:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n{todo_json}"&lt;/span&gt;
                &lt;span class="p"&gt;))]))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;McpError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;internal_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create todo: {e}"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Other tools: get_todo, list_todos, update_todo, delete_todo...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database query for creating a todo item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CreateTodoRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;r#"
        INSERT INTO todos (title, completed, client_id)
        VALUES ($1, COALESCE($2, FALSE), $3)
        RETURNING id, title, completed
        "#&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.fetch_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="py"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="py"&gt;.completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integrating the SSE Server
&lt;/h2&gt;

&lt;p&gt;So far, we've defined our &lt;code&gt;MCP&lt;/code&gt; service &lt;code&gt;struct&lt;/code&gt;, implemented the &lt;code&gt;ServerHandler&lt;/code&gt; trait, and used the &lt;code&gt;#[tool_router]&lt;/code&gt; macro to register our tools. Now, we need to integrate this service into an SSE server so it can be accessed from a URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SocketAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;shuttle_runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ---- Other boilerplate code ----&lt;/span&gt;

    &lt;span class="c1"&gt;// Create SSE server configuration for MCP&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sse_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SseServerConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sse_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"/mcp/sse"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;post_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"/mcp/message"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;sse_keep_alive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Create SSE server&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sse_server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sse_router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SseServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sse_config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create protected SSE routes (require authorization)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;protected_sse_router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sse_router&lt;/span&gt;&lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_fn_with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="c1"&gt;// Applying the middleware for authentication&lt;/span&gt;
        &lt;span class="n"&gt;validate_token_middleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Create HTTP router with auth routes (non-protected) and protected SSE router&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth_router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protected_sse_router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cors_layer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add the `TodoService` we created to the SSE server&lt;/span&gt;
    &lt;span class="n"&gt;sse_server&lt;/span&gt;&lt;span class="nf"&gt;.with_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nn"&gt;TodoService&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())));&lt;/span&gt;

    &lt;span class="c1"&gt;// ---- Other boilerplate code to start the server ----&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware is applied to make sure only authenticated clients can access the MCP route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;protected_sse_router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sse_router&lt;/span&gt;&lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_fn_with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="c1"&gt;// Applying the middleware for authentication&lt;/span&gt;
    &lt;span class="n"&gt;validate_token_middleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also used Axum's &lt;code&gt;with_service&lt;/code&gt; method to attach our &lt;code&gt;TodoService&lt;/code&gt; to the route. This makes our service available to any client that connects to the SSE server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;sse_server&lt;/span&gt;&lt;span class="nf"&gt;.with_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nn"&gt;TodoService&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_state&lt;/span&gt;&lt;span class="py"&gt;.pool&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment and Testing
&lt;/h2&gt;

&lt;p&gt;Deploy to Shuttle with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding to MCP Clients
&lt;/h3&gt;

&lt;p&gt;Follow the instructions below to connect your specific MCP client to the server:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Todo List"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-server.shuttle.app/mcp/sse"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Cursor, navigate to the Tools page within the Settings menu. Then click the &lt;strong&gt;"Login"&lt;/strong&gt; button to authenticate with the MCP server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrg9wjh2nhzbt1afdrg0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrg9wjh2nhzbt1afdrg0.png" alt="MCP server requires login" width="673" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will redirect you to the authorization page that we created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuooi4seshw6u9ldmlkx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuooi4seshw6u9ldmlkx2.png" alt="MCP server authorization page" width="752" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After authorizing the client, you'll be redirected to Cursor, which will now have access to the MCP tools on your account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrgs25ob275m0y2hfkhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrgs25ob275m0y2hfkhv.png" alt="MCP server connected" width="668" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! 🎉 Our MCP server is now hosted and ready to use by Cursor. The same process applies to any other MCP client, such as Claude Code or Windsurf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've successfully built a production-ready MCP server with the SSE transport type that combines the power of Server-Sent Events with robust OAuth 2 authentication. This project demonstrates how to create secure, real-time AI tool access that goes far beyond a simple local setup.&lt;/p&gt;

&lt;p&gt;Our implementation delivers a complete OAuth 2 flow to secure all client interactions. The SSE-based protocol enables instant tool communication, while the authorization layer ensures that actions are performed securely on behalf of specific users. The entire system, backed by &lt;strong&gt;PostgreSQL&lt;/strong&gt;, deploys seamlessly to &lt;strong&gt;Shuttle&lt;/strong&gt;, providing a solid foundation for building any real-world MCP service.&lt;/p&gt;

&lt;p&gt;Pushing future updates is as simple as running &lt;code&gt;shuttle deploy&lt;/code&gt;, and you can easily integrate this command into a &lt;a href="https://docs.shuttle.dev/integrations/ci-cd" rel="noopener noreferrer"&gt;CI/CD pipeline&lt;/a&gt; to fully automate the process.&lt;/p&gt;

&lt;p&gt;The combination of Rust's performance, Shuttle's deployment simplicity, and MCP's standardized protocol creates a compelling stack for modern AI tooling infrastructure that scales from prototype to production without compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it Yourself
&lt;/h2&gt;

&lt;p&gt;Ready to build your own SSE MCP server? Run the following command to clone the complete project and deploy with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the project&lt;/span&gt;
shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; mcp/mcp-sse-oauth

&lt;span class="c"&gt;# Navigate to the project directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-sse-oauth

&lt;span class="c"&gt;# Deploy the project&lt;/span&gt;
shuttle deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;a href="https://docs.shuttle.dev" rel="noopener noreferrer"&gt;Shuttle documentation&lt;/a&gt; for more information. Happy coding!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>mcp</category>
      <category>sse</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Backend Challenge: Learn Rust Microservices for the Cloud</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Wed, 27 Aug 2025 14:02:48 +0000</pubDate>
      <link>https://dev.to/shuttle/backend-challenge-learn-rust-microservices-for-the-cloud-30dj</link>
      <guid>https://dev.to/shuttle/backend-challenge-learn-rust-microservices-for-the-cloud-30dj</guid>
      <description>&lt;p&gt;Looking to level up your Rust skills while learning modern cloud deployment? ShellCon offers a unique hands-on learning experience that combines Rust microservices development with Shuttle Cloud deployment. This comprehensive full-stack project will take you from local development to cloud deployment while solving real-world performance optimization problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is ShellCon?
&lt;/h3&gt;

&lt;p&gt;ShellCon is an innovative learning approach that transforms Rust and cloud development education into a practical, project-based experience. Instead of working through abstract tutorials, you'll build and optimize a complete microservices architecture with three interconnected Rust services and a React dashboard.&lt;/p&gt;

&lt;h4&gt;
  
  
  Development Workflow
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Local Development&lt;/strong&gt; - Set up and run all three services locally, then verify the React dashboard connects properly to each service endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Performance Optimization&lt;/strong&gt; - Solve four distinct performance challenges across the microservices, each focusing on different aspects of Rust optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Cloud Deployment&lt;/strong&gt; - Deploy your optimized services to Shuttle Cloud and validate everything works in a production environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You'll Build
&lt;/h3&gt;

&lt;p&gt;You'll construct a complete microservices ecosystem consisting of three specialized Rust services working together through REST APIs. Your development journey involves three critical phases:&lt;/p&gt;

&lt;h4&gt;
  
  
  The Microservices Architecture
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;aqua-monitor&lt;/strong&gt;: An environmental monitoring service that handles sensor data collection, tank readings, and system status endpoints. You'll work with async I/O patterns and HTTP client optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;species-hub&lt;/strong&gt;: A database service managing species information, feeding schedules, and related data. This service teaches SQL optimization and efficient database interaction patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aqua-brain&lt;/strong&gt;: An analytics engine that processes data from the other services to generate insights and analysis. You'll focus on memory optimization and efficient string handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React Dashboard&lt;/strong&gt;: A modern frontend that connects to all three services through REST APIs, providing real-time monitoring and control capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You'll Learn
&lt;/h3&gt;

&lt;p&gt;ShellCon offers a comprehensive learning experience that combines project-based learning with real-world architecture. You'll build a complete application with production-like microservices while getting immediate feedback on your optimizations.&lt;/p&gt;

&lt;p&gt;Through this hands-on approach, you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust Development:&lt;/strong&gt; Async/await patterns, non-blocking I/O, SQLx with PostgreSQL, heap allocation optimization, and Axum connection pooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Infrastructure:&lt;/strong&gt; Shuttle Cloud deployment, resilient microservices creation, Docker containers, and multi-stage deployment configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Integration:&lt;/strong&gt; Connecting React to Rust microservices, designing efficient RESTful endpoints, and implementing cross-service health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Challenge Structure
&lt;/h3&gt;

&lt;p&gt;Each challenge focuses on a specific aspect of Rust performance optimization:&lt;/p&gt;

&lt;h4&gt;
  
  
  Four Key Optimization Challenges
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1. Async I/O (aqua-monitor)&lt;/strong&gt;: Fix blocking operations in async code to improve tank readings performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SQL Queries (species-hub)&lt;/strong&gt;: Optimize database queries to enhance species database efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Memory Management (aqua-brain)&lt;/strong&gt;: Minimize heap allocations in the analytics engine using Rust's ownership system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. HTTP Client Pooling (aqua-monitor)&lt;/strong&gt;: Implement connection reuse patterns to boost network performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started is Simple
&lt;/h2&gt;

&lt;p&gt;Ready to begin your Rust and cloud development journey? The setup process is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository&lt;/strong&gt; and install prerequisites (Rust, Docker, Node.js, Shuttle CLI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch the three microservices&lt;/strong&gt; locally using the provided scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start the React dashboard&lt;/strong&gt; and verify all connections work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Begin solving the four optimization challenges&lt;/strong&gt; at your own pace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to Shuttle Cloud&lt;/strong&gt; and validate your solutions in production&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Start Your Rust Development Journey Today
&lt;/h3&gt;

&lt;p&gt;The demand for Rust developers continues to grow as more companies adopt Rust for performance-critical applications. ShellCon provides the perfect combination of hands-on learning and real-world application that will prepare you for professional Rust development. Don't spend months reading documentation without building anything meaningful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to build production-ready microservices and master Rust optimization?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/shuttle-hq/shuttle-shellcon.git" rel="noopener noreferrer"&gt;Clone the repository&lt;/a&gt; and start building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/shuttle-hq/shuttle-shellcon.git
&lt;span class="nb"&gt;cd &lt;/span&gt;shuttle-shellcon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your journey from Rust beginner to confident microservices developer starts with a single git clone. Start building today and master the skills that will define your backend development career!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Join developers worldwide who are mastering Rust and cloud development through ShellCon's practical, project-based approach. Start building today and develop the microservices skills that will advance your backend development career.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>mcp</category>
      <category>cursor</category>
    </item>
    <item>
      <title>How to Build a stdio MCP Server in Rust</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Tue, 26 Aug 2025 14:01:33 +0000</pubDate>
      <link>https://dev.to/shuttle/how-to-build-a-stdio-mcp-server-in-rust-5583</link>
      <guid>https://dev.to/shuttle/how-to-build-a-stdio-mcp-server-in-rust-5583</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI agents are transforming how we work, but they're often limited by their training data. &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP (Model Context Protocol)&lt;/a&gt; servers bridge this gap by giving AI agents access to real-time data, external APIs, and custom tools.&lt;/p&gt;

&lt;p&gt;Building your own MCP server lets you extend AI capabilities with your specific tools and services. In this guide, we'll create a DNS lookup MCP server in &lt;strong&gt;Rust&lt;/strong&gt; that demonstrates these concepts in action.&lt;/p&gt;

&lt;p&gt;Our MCP server will perform DNS lookups and provide real-time internet data to AI agents. Since AI models can't directly access external data sources, the server acts as a bridge, delivering live DNS information that extends the agent's capabilities beyond its training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MCP?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; stands for &lt;strong&gt;Model Context Protocol&lt;/strong&gt;, it's a protocol that allows AI models to securely access external tools and data sources. This is extremely useful, because the AI agents will not be limited to the data they were trained on, in fact they'll be able to access external data and resources plus the ability to use tools and make API calls to external services.&lt;/p&gt;

&lt;p&gt;Once you build an MCP server, it can be used by any MCP client, like &lt;strong&gt;Cursor&lt;/strong&gt; and &lt;strong&gt;Claude Code&lt;/strong&gt;, or any other MCP client that supports the MCP protocol like chatbots and conversational agents. The protocol is a standard, so you can use the same MCP server with different clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Transport Types
&lt;/h3&gt;

&lt;p&gt;MCP servers use two transport mechanisms: &lt;strong&gt;stdio&lt;/strong&gt; (standard input/output) and &lt;strong&gt;SSE&lt;/strong&gt; (Server-Sent Events). The stdio transport communicates through standard input/output, similar to &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;LSP (Language Server Protocol)&lt;/a&gt; servers, making it fast and efficient for local use. These servers are installed locally and have full access to your machine, for example they can interact with the file system, execute shell commands, access databases, or interact with any local services.&lt;/p&gt;

&lt;p&gt;SSE servers run externally in the cloud and connect via WebSockets. While they offer more scalability, they require network setup and don't have access to your local machine's resources.&lt;/p&gt;

&lt;p&gt;This tutorial focuses on building a &lt;strong&gt;stdio MCP server&lt;/strong&gt; - the simpler approach that's perfect for local development and personal AI workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building stdio MCP Server in Rust
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we're going to build a simple MCP server using the stdio transport that allows AI agents to perform DNS lookups.&lt;/p&gt;

&lt;p&gt;We'll use the &lt;strong&gt;HackerTarget&lt;/strong&gt; API (a public service that provides DNS lookup capabilities) to perform the DNS lookups and return the results back to the AI agent.&lt;/p&gt;

&lt;p&gt;The workflow is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI agent asks the MCP server to perform a DNS lookup with a parameter: &lt;code&gt;domain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP server will use the &lt;strong&gt;HackerTarget&lt;/strong&gt; API to perform the DNS lookup&lt;/li&gt;
&lt;li&gt;MCP server will return the results back to the AI agent&lt;/li&gt;
&lt;li&gt;The AI agent will read the real-time results, providing important context for the next steps&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting Up the Project
&lt;/h3&gt;

&lt;p&gt;Now that we have laid out the plan and workflow, let's start building the &lt;strong&gt;MCP server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, we need to create a new Rust project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo new dns-lookup-mcp-server
&lt;span class="nb"&gt;cd &lt;/span&gt;dns-lookup-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's install the required dependencies for the project, the &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; provides an official SDK for &lt;strong&gt;Rust&lt;/strong&gt; called &lt;code&gt;rmcp&lt;/code&gt;. You can find the Rust SDK for MCP here: &lt;a href="https://github.com/modelcontextprotocol/rust-sdk" rel="noopener noreferrer"&gt;github.com/modelcontextprotocol/rust-sdk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add the following dependencies to your &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dns-lookup-mcp-server"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;rmcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"transport-io"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;reqwest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.12"&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="py"&gt;schemars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;serde&lt;/code&gt; crate to serialize and deserialize JSON-RPC (JSON Remote Procedure Call) data for MCP protocol&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tokio&lt;/code&gt; for async operations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reqwest&lt;/code&gt; to make HTTP requests to the DNS lookup API (HackerTarget)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schemars&lt;/code&gt; for JSON schema generation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anyhow&lt;/code&gt; for error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;rmcp&lt;/code&gt; crate provides the MCP server implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the DNS Service
&lt;/h2&gt;

&lt;p&gt;Let's create the DNS service module. First, create a new file &lt;code&gt;src/dns_mcp.rs&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rmcp&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;server&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ToolRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;model&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;ErrorData&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;schemars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ServerHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;borrow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;future&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DnsService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DnsService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DnsLookupRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[schemars(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"The domain name to lookup"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tool_router]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;DnsService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tool_router&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[tool(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Perform DNS lookup for a domain name"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;dns_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DnsLookupRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"https://api.hackertarget.com/dnslookup/?q={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.domain&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32603&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;McpError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32603&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read response: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)]))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code uses several key attributes that handle MCP protocol integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#[tool_router]&lt;/code&gt; wires up the MCP protocol for any methods marked with &lt;code&gt;#[tool]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[tool(description = "...")]&lt;/code&gt; exposes the function to AI models with a description for tool selection.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Parameters&amp;lt;T&amp;gt;&lt;/code&gt; provides type-safe parameter extraction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[schemars(description = "...")]&lt;/code&gt; adds schema documentation for AI parameter generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Server Configuration
&lt;/h2&gt;

&lt;p&gt;To configure your MCP server, implement &lt;code&gt;ServerHandler&lt;/code&gt; for metadata and apply &lt;code&gt;#[tool_handler]&lt;/code&gt; to auto-generate tool discovery.&lt;/p&gt;

&lt;p&gt;Add this to the end of &lt;code&gt;src/dns_mcp.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool_handler]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ServerHandler&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;DnsService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;protocol_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;ProtocolVersion&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;V_2024_11_05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;ServerCapabilities&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.enable_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;server_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Implementation&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_build_env&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A DNS lookup service that queries domain information using the HackerTarget API. Use the dns_lookup tool to perform DNS lookups for any domain name."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the Server
&lt;/h2&gt;

&lt;p&gt;Then, set up &lt;code&gt;stdio&lt;/code&gt; communication in &lt;code&gt;src/main.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;dns_mcp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DnsService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rmcp&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ServiceExt&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;dns_mcp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create an instance of our DNS service&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DnsService&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="nf"&gt;.waiting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing During Development
&lt;/h2&gt;

&lt;p&gt;To speed up your development and testing, you can run the MCP server directly from your project without building a release binary. In Cursor, enable this by creating a &lt;code&gt;.cursor/mcp.json&lt;/code&gt; file in your project's root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DNS Lookup (Dev)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cargo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that configuration may vary depending on your MCP client. Check your client's documentation for the specific configuration format.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Building for Production
&lt;/h2&gt;

&lt;p&gt;To build and install the MCP server for production use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build and install the binary to ~/.cargo/bin&lt;/span&gt;
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the &lt;code&gt;dns-lookup-mcp-server&lt;/code&gt; binary to your Cargo bin directory (typically &lt;code&gt;~/.cargo/bin&lt;/code&gt;), making it available system-wide if this directory is in your PATH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using in Cursor
&lt;/h2&gt;

&lt;p&gt;To configure the MCP server globally for Cursor, edit the main settings file located at &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DNS Lookup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dns-lookup-mcp-server"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the binary name directly, assuming you've installed it with &lt;code&gt;cargo install --path .&lt;/code&gt; and &lt;code&gt;~/.cargo/bin&lt;/code&gt; is in your PATH.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Server in Action
&lt;/h2&gt;

&lt;p&gt;Time to put the MCP server to work. Let's have it check the DNS records for three domains and create a table of the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0dnkv7t8xee20rlojqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0dnkv7t8xee20rlojqu.png" alt="AI assistant using DNS lookup MCP server to query domain records" width="602" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above screenshot, we asked the AI agent to query the DNS records for three domains and create a markdown table to display the results.&lt;/p&gt;

&lt;p&gt;The AI agent was able to use the &lt;code&gt;dns_lookup&lt;/code&gt; tool to perform the DNS lookup and return the results back to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While AI can significantly improve your development workflow, LLMs are naturally limited by their training data, knowledge cutoffs, and sandbox environment. MCP servers bridge this gap by giving AI agents access to real-time data and tools they can use when needed.&lt;/p&gt;

&lt;p&gt;This feature is essential for data that changes too quickly for static training sets. For instance, to configure an application's networking, your AI agent needs direct access to the very latest DNS records, not outdated information. The same logic applies to other live sources, like querying a real-time database or checking an API's current status.&lt;/p&gt;

&lt;p&gt;Rust shines as a great language for building MCP servers. Its type safety and performance characteristics make it ideal for creating reliable tools that AI models can use confidently. The &lt;code&gt;rmcp&lt;/code&gt; crate's clean API combined with macros like &lt;code&gt;#[tool]&lt;/code&gt; eliminates boilerplate, letting you focus on functionality rather than protocol details.&lt;/p&gt;

&lt;p&gt;In just a few dozen lines of code, we built a production-ready DNS lookup service that works with any MCP-compatible AI client. The standardized MCP protocol means your Rust server can integrate seamlessly across different AI platforms and tools.&lt;/p&gt;

&lt;p&gt;This is just the beginning - you can extend this pattern to build MCP servers for databases, APIs, file systems, or any external service your AI agents might need to interact with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy your MCP server to the cloud
&lt;/h2&gt;

&lt;p&gt;Ready to extend AI capabilities beyond their training data? Our &lt;strong&gt;SSE MCP server template&lt;/strong&gt; lets you build production-ready MCP servers with your own custom logic that seamlessly integrate with any AI client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; mcp/mcp-sse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rust</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Assisted Rust Development Environment Setup: Build Rust APIs in Minutes</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Mon, 18 Aug 2025 13:38:28 +0000</pubDate>
      <link>https://dev.to/shuttle/ai-assisted-rust-development-environment-setup-build-rust-apis-in-minutes-2oh6</link>
      <guid>https://dev.to/shuttle/ai-assisted-rust-development-environment-setup-build-rust-apis-in-minutes-2oh6</guid>
      <description>&lt;p&gt;Shipping Rust code used to mean wrestling with the borrow-checker, poring over docs, and SSH-ing into servers at 2 a.m. In 2025 that workflow feels prehistoric. With AI-first IDEs such as Cursor, tool-calling standards like &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;, and one-command cloud hosts such as &lt;a href="https://www.shuttle.dev" rel="noopener noreferrer"&gt;Shuttle&lt;/a&gt;, you can spin up, test, and deploy a secure Rust API in 5 minutes before your coffee cools.&lt;/p&gt;

&lt;p&gt;This guide shows you how to assemble an AI-assisted development environment tailored for Rust: code completion that understands your entire repo, agents that run migrations and push containers, and MCP plugins that read the latest docs instead of hallucinated snippets. We'll finish by launching a task-management API live to the cloud: hands-off coding, testing, and shipping in under five minutes using a true 10x developer workflow.&lt;/p&gt;

&lt;p&gt;While this sounds cool, AI-assisted Rust development doesn't replace human developers. Treat it like a power tool: it boosts speed and efficiency, yet our craft still rests on understanding core programming concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What IDE Should You Use?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; is one of the most popular AI-powered code editors. Forked from VS Code, it keeps everything you love about VS Code while layering in AI-first features.&lt;/p&gt;

&lt;p&gt;With Cursor, you tap into today's leading large-language models, and the built-in AI assistant writes code with full, context-aware knowledge of your entire codebase.&lt;/p&gt;

&lt;p&gt;Features like &lt;strong&gt;Codebase Indexing&lt;/strong&gt; can help the AI agents to search through your codebase with ease, and &lt;strong&gt;Cursor Tab&lt;/strong&gt; will help you write code much faster and more efficiently by recommending code completions and edits that you can accept with just a tab press. You can think of it as an upgraded successor to what GitHub Copilot once was for AI-assisted development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your AI-Enhanced Development Environment
&lt;/h2&gt;

&lt;p&gt;In this section, we'll explore how to build an AI-powered Rust development environment that handles everything from coding to cloud deployment. While we'll use Cursor as our main IDE, most techniques work with any AI coding tool, so don't worry if you're using different tools, be it &lt;strong&gt;Windsurf&lt;/strong&gt;, &lt;strong&gt;Claude Code&lt;/strong&gt;, or even a web based AI chatbot for rust api development.&lt;/p&gt;

&lt;p&gt;We'll set up &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;MCP (Model Context Protocol) servers&lt;/a&gt; that let AI assistants interact with external systems, explore Cursor's advanced features like codebase indexing and tab completion, and cover universal strategies for effective AI-assisted development. By the end, you'll have an environment where AI can autonomously write, test, and deploy your Rust applications with automated deployment rust workflows. &lt;strong&gt;(YES! Even deployments!)&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Servers: Extending AI Capabilities
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What Are MCP Servers?
&lt;/h4&gt;

&lt;p&gt;LLMs excel at generating text, but their fixed knowledge cut-off means they often lack your project's freshest context. To bridge that gap, you need agents that can search databases, run shell commands, and pull live data from APIs. That's where MCP servers come in.&lt;/p&gt;

&lt;p&gt;MCP servers are specialized tools that dramatically extend your AI assistant's capabilities beyond simple code generation. Think of MCP servers as powerful plugins that give your AI assistant the ability to interact with external systems, access real-time data, execute commands, and perform complex operations that would otherwise require manual intervention.&lt;/p&gt;

&lt;h4&gt;
  
  
  What MCP Servers Can Do for You
&lt;/h4&gt;

&lt;p&gt;MCP servers transform your AI assistant from a simple code generator into a comprehensive development partner. Instead of just writing code, your AI can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run database queries:&lt;/strong&gt; Fetch real time data from databases and use them to generate code or give you answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perform searches&lt;/strong&gt; using search engines or searching documentations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute system commands&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interact with APIs&lt;/strong&gt; and external services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor application logs&lt;/strong&gt; and debug production issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy applications directly&lt;/strong&gt; to cloud platforms like Shuttle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP servers aren't confined to a single flavor: there's an ecosystem of ready-made options to choose from, and you can even build your own to run fully custom logic.&lt;/p&gt;

&lt;p&gt;The screenshot below shows MCP servers in action. Notice how the AI assistant taps the specialized tool to deploy the Rust project to Shuttle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f5ka7cnnmcte5u3bxe3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f5ka7cnnmcte5u3bxe3.png" alt="AI assistant using Shuttle MCP server deploy tool call" width="726" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, you can see the AI assistant using the &lt;code&gt;deploy&lt;/code&gt; tool to deploy the application to the cloud, demonstrating how MCP servers extend the AI's capabilities beyond simple code generation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Later in the article, we'll walk through how to use the Shuttle MCP server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Recommended MCP Servers for Rust Development
&lt;/h4&gt;

&lt;p&gt;Here are the essential MCP servers that will supercharge your Rust development workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://context7.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Context7 MCP&lt;/strong&gt;&lt;/a&gt; - Context7 solves one of the biggest problems in AI-assisted development: outdated documentation. Instead of relying on generic or outdated information from LLM training data, Context7 pulls up-to-date, version-specific documentation and code examples directly from the source. This means your AI assistant gets accurate, relevant information about the exact versions of libraries you're using, eliminating hallucinations and providing working code examples.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shuttle-hq/shuttle/tree/main/mcp" rel="noopener noreferrer"&gt;&lt;strong&gt;Shuttle MCP&lt;/strong&gt;&lt;/a&gt; - Shuttle is a cloud platform specifically designed for Rust applications. This MCP server streamlines your entire deployment workflow, enabling seamless project creation, application deployment, environment management, and real-time monitoring directly within your development environment.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/server-puppeteer" rel="noopener noreferrer"&gt;&lt;strong&gt;Puppeteer MCP&lt;/strong&gt;&lt;/a&gt; - A Model Context Protocol server that provides browser automation capabilities using Puppeteer. This server enables LLMs to interact with web pages, take screenshots, and execute JavaScript in a real browser environment.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/github-mcp-server" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub MCP&lt;/strong&gt;&lt;/a&gt; - Integrates version control operations directly into your AI workflow. Manages code repositories, handles pull requests, tracks issues, and coordinates collaborative development without switching contexts.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/playwright-mcp" rel="noopener noreferrer"&gt;&lt;strong&gt;Playwright MCP&lt;/strong&gt;&lt;/a&gt; - Brings Microsoft's browser automation framework to your AI assistant. Enables cross-platform web testing across Chrome, Firefox, Safari, and Edge with enhanced reliability compared to traditional testing tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cursor Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Enable Memories
&lt;/h4&gt;

&lt;p&gt;Cursor provides a feature called &lt;strong&gt;Memories&lt;/strong&gt;, this feature let's the AI agent to remember specific context about you and your project, using this feature for a while can make your AI agent more personalized and aware of your coding style, preferences and the projects you're working on.&lt;/p&gt;

&lt;p&gt;To enable this feature, navigate to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Rules&lt;/strong&gt; &amp;gt; &lt;strong&gt;Memories&lt;/strong&gt; and tick the &lt;strong&gt;Generate memories&lt;/strong&gt; option.&lt;/p&gt;

&lt;h4&gt;
  
  
  Add Documentation Context
&lt;/h4&gt;

&lt;p&gt;AI coding assistants are more powerful when they have a good understanding of the libraries and frameworks you're using, the more context, the better results you'll get. However, copy/pasting documentation in every chat is a tedious task and we don't wanna do that by hand.&lt;/p&gt;

&lt;p&gt;Thankfully, Cursor provides us with a great developer friendly feature that allows us to add documentation context only once and we'll be able to use it as context in any chat we want.&lt;/p&gt;

&lt;p&gt;To add documentation context:&lt;/p&gt;

&lt;p&gt;Open the chat panel in Cursor and type &lt;code&gt;@Docs&lt;/code&gt; in the chat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fraiiiv63irfqo1rbk64n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fraiiiv63irfqo1rbk64n.png" alt="Cursor chat panel showing the @Docs command" width="499" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Add new doc".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl9ymtizc7tm3r56mfan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl9ymtizc7tm3r56mfan.png" alt="Cursor docs interface showing the 'Add new doc' button" width="471" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the URL prefix of the documentation you want to add (e.g., &lt;code&gt;https://docs.shuttle.dev&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m1innrxlei5cc8xdma5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m1innrxlei5cc8xdma5.png" alt="Cursor docs interface showing URL input field for documentation" width="785" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Enter&lt;/strong&gt;, this will bring us to the last step, which we can specify a &lt;strong&gt;title&lt;/strong&gt; for our documentation, along with an &lt;strong&gt;entrypoint URL&lt;/strong&gt; and &lt;strong&gt;prefix URL&lt;/strong&gt; for the documentation we want to add.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcl3lsg5ig2dz9q5tet7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcl3lsg5ig2dz9q5tet7.png" alt="Cursor docs configuration dialog showing title, entrypoint URL, and prefix URL fields" width="749" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cursor will automatically index the page and any sub-pages with that URL prefix. We can then mention it using &lt;code&gt;@Docs&lt;/code&gt; and select the specific documentation we want to use. Claude 4.0 is especially good at this due to its large context window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7089dzpsktq47i9c6y4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7089dzpsktq47i9c6y4.png" alt="Cursor chat interface showing how to mention docs using @Docs" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cursor will automatically fetch the relevant documents and feed it to the AI assistant, we'll get an answer almost immediately, this saves us a lot of time and effort to search through documentation manually.&lt;/p&gt;

&lt;p&gt;Recommended documentation to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust standard library: &lt;code&gt;https://doc.rust-lang.org/std&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Axum framework: &lt;code&gt;https://docs.rs/axum/latest/axum&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SQLx: &lt;code&gt;https://docs.rs/sqlx/latest/sqlx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Shuttle: &lt;code&gt;https://docs.shuttle.dev&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures the AI assistant understands the specific APIs, patterns, and best practices for our tech stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rust-Specific Rules for AI Agents
&lt;/h3&gt;

&lt;p&gt;Rules are yet another AI feature provided by Cursor that allows us to define custom rules that guide how the AI writes code for our specific project.&lt;/p&gt;

&lt;p&gt;Most developers skimp on documentation and pay for it later. &lt;strong&gt;Cursor's Rules&lt;/strong&gt; feature flips that script: You drop a markdown file at &lt;code&gt;.cursor/rules/my-rule.mdc&lt;/code&gt; into your repo, and the AI treats those notes as living docs. No more "I'll document it later" guilt 🙂 Your guidelines, style tips, and architectural clues are always in context, keeping the assistant (and your teammates) perfectly informed.&lt;/p&gt;

&lt;p&gt;For that reason, we have &lt;strong&gt;Cursor Rules&lt;/strong&gt;, Cursor allows us to define a set of custom rules that sets some rules for the AI agent to follow when writing code for a specific project.&lt;/p&gt;

&lt;p&gt;Rules can be specified to projects, and they can be specified to be read automatically or manually.&lt;/p&gt;

&lt;p&gt;Let's create a &lt;code&gt;.cursor/rules/rust.mdc&lt;/code&gt; file in our project root with Rust-specific guidelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
|-- .cursor
|   |-- rules
|       |-- rust.mdc
|       |-- ...
|-- ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an expert Rust developer. Follow these rules when writing Rust code:

&lt;span class="gu"&gt;## Code Style&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use idiomatic Rust patterns and conventions
&lt;span class="p"&gt;-&lt;/span&gt; Prefer explicit error handling with Result&lt;span class="nt"&gt;&amp;lt;T&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;E&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use ? operator for error propagation
&lt;span class="p"&gt;-&lt;/span&gt; Implement proper ownership and borrowing
&lt;span class="p"&gt;-&lt;/span&gt; Use descriptive variable names and function signatures

&lt;span class="gu"&gt;## Error Handling&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; For comprehensive error handling, use libraries like &lt;span class="sb"&gt;`anyhow`&lt;/span&gt; for simple error handling, &lt;span class="sb"&gt;`thiserror`&lt;/span&gt; for custom error types, or &lt;span class="sb"&gt;`eyre`&lt;/span&gt; for enhanced error reporting
&lt;span class="p"&gt;-&lt;/span&gt; Never use unwrap() or expect() in production code
&lt;span class="p"&gt;-&lt;/span&gt; Provide meaningful error messages
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`anyhow::Result&amp;lt;T&amp;gt;`&lt;/span&gt; for functions that can return multiple error types
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`thiserror::Error`&lt;/span&gt; derive macro for custom error enums

&lt;span class="gu"&gt;## Testing&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Write unit tests for all public functions
&lt;span class="p"&gt;-&lt;/span&gt; Use descriptive test names that explain the scenario
&lt;span class="p"&gt;-&lt;/span&gt; Group related tests in modules

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Important Note: These are generic rules for Rust development. You should customize these rules for your specific project. For example, if you're using a validation library like validator, write specific rules about how to handle validations. If you're using specific architectural patterns or frameworks, document them in your rules so the AI can follow your project's conventions consistently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  How Cursor Reads Your Specified Rules
&lt;/h4&gt;

&lt;p&gt;Cursor will read these rules based on a few conditions that we can manually specify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always&lt;/strong&gt;: This will read the rules for every single chat we have with the AI assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Attached&lt;/strong&gt;: This will read the rules based on a specified glob pattern, for example, if we want the rules to be read for every rust file, we can specify the glob pattern to be &lt;code&gt;*/*.rs&lt;/code&gt;. Or if we want to add rules for a specific crate, we can specify &lt;code&gt;crates/my_macros/**/*.rs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Requested&lt;/strong&gt;: This gives us the option to add a description to the rules file, Cursor will automatically read these rules if it finds a description that matches the current chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual&lt;/strong&gt;: This will read the rules only when we manually mention it in the chat by typing &lt;code&gt;@Cursor Rules&lt;/code&gt; and selecting the rules that we defined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7q5a3d1265xji7lkywe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7q5a3d1265xji7lkywe.png" alt="Cursor rules configuration interface showing different rule attachment options" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices for AI Prompting
&lt;/h3&gt;

&lt;p&gt;The general principle of &lt;strong&gt;garbage in, garbage out&lt;/strong&gt; applies in prompt engineering as well, the more specific and detailed we are, the better the results we'll get.&lt;/p&gt;

&lt;p&gt;Here are some best practices for AI prompting:&lt;/p&gt;

&lt;h4&gt;
  
  
  Use a Todo-Driven Approach
&lt;/h4&gt;

&lt;p&gt;It's very common for the AI assistant to write some good code for a feature, only to delete, or modify it later and therefore breaking your code.&lt;/p&gt;

&lt;p&gt;Instead of manually writing prompts each time, we can create a file for each task that we want to do, this file will contain all the tasks that have been done and that are pending for the specific feature that we want to build.&lt;/p&gt;

&lt;p&gt;This way, our AI assistant will have a complete context and understanding of what we're trying to build and what we have done so far.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;todo.md&lt;/code&gt; file in your project root that explains your application requirements and features, then break them down into smaller, actionable tasks. Next time you want to work on a feature, you can just mention the &lt;code&gt;todo.md&lt;/code&gt; file and the relevant file names that need to be changed and the AI assistant will work through the tasks systematically.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project: Task Management API&lt;/span&gt;

This API will be a simple task management API, it will have a few endpoints:
&lt;span class="p"&gt;
-&lt;/span&gt; GET /tasks - List all tasks with optional filtering
&lt;span class="p"&gt;-&lt;/span&gt; POST /tasks - Create a new task
&lt;span class="p"&gt;-&lt;/span&gt; GET /tasks/{id} - Get a specific task
&lt;span class="p"&gt;-&lt;/span&gt; PUT /tasks/{id} - Update a task
&lt;span class="p"&gt;-&lt;/span&gt; DELETE /tasks/{id} - Delete a task

&lt;span class="gu"&gt;## Requirements&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; CRUD operations for tasks
&lt;span class="p"&gt;-&lt;/span&gt; User authentication
&lt;span class="p"&gt;-&lt;/span&gt; Database persistence
&lt;span class="p"&gt;-&lt;/span&gt; REST API endpoints

&lt;span class="gu"&gt;## Tasks&lt;/span&gt;

&lt;span class="gu"&gt;### Project Setup&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Initialize Rust project with dependencies
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Set up database connection and migrations

&lt;span class="gu"&gt;### Authentication&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Create User struct and auth endpoints
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Implement JWT middleware and password hashing

&lt;span class="gu"&gt;### Task CRUD&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Create Task model
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Create the task CRUD endpoints
&lt;span class="p"&gt;  -&lt;/span&gt; [ ] GET /tasks - List all tasks with optional filtering
&lt;span class="p"&gt;  -&lt;/span&gt; [ ] POST /tasks - Create a new task
&lt;span class="p"&gt;  -&lt;/span&gt; [ ] GET /tasks/{id} - Get a specific task
&lt;span class="p"&gt;  -&lt;/span&gt; [ ] PUT /tasks/{id} - Update a task
&lt;span class="p"&gt;  -&lt;/span&gt; [ ] DELETE /tasks/{id} - Delete a task
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Add input validation and error handling for the task CRUD endpoints

&lt;span class="gu"&gt;### Testing&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Write unit and integration tests
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Set up test database

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Provide Focused Context
&lt;/h4&gt;

&lt;p&gt;Give the AI only the necessary context for the task at hand. Too much context can cause the AI to lose focus or forget important details. Be selective about which files and information you include.&lt;/p&gt;

&lt;h4&gt;
  
  
  Know When to Use MCP Servers vs CLIs
&lt;/h4&gt;

&lt;p&gt;MCP servers and CLIs are both great tools that your AI agent can use to interact with systems, both of them have their pros and cons and you should know when to use each of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use MCP servers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP servers can be superior to CLIs because the AI understands how to use them properly. While the AI might struggle with lesser-known CLI tools, MCP servers provide structured interfaces that the AI can navigate confidently. AI agents might not have the proper context for the CLIs and how to use them, therefore it has a more potential for hallucinations and making errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use CLIs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're using a popular CLI tool like &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;cargo&lt;/code&gt;, etc. In that case, you won't need a MCP server for those actions, most LLMs have quite good knowledge on these tools and how they work.&lt;/p&gt;

&lt;p&gt;Another reason to choose CLIs over MCP servers is that CLIs can stream their output and you don't need to wait for the processes to finish, you can see the code execution in real time, this streaming capability isn't available in MCP servers, on the contrary, you'll have to wait for the execution to finish before you can see the results.&lt;/p&gt;

&lt;h4&gt;
  
  
  Build Custom MCP Servers
&lt;/h4&gt;

&lt;p&gt;If you already have an app or API, you can spin up a custom MCP server with the official SDKs, available in Rust and several other languages. A quick look at the docs shows just how straightforward the process is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Complete Rust CRUD API with AI in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;Now for the exciting part—let's build and deploy a complete CRUD API using nothing but AI assistance. We'll use all the tools and techniques we've learned so far to build a task management API with full database integration using the Axum rust framework.&lt;/p&gt;

&lt;p&gt;We'll also deploy the API to the cloud, we'll do all this without writing any code, we'll just let our AI assistant to do all the heavy lifting for us.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important Note&lt;/p&gt;

&lt;p&gt;This approach is not a complete replacement for human developers, AI can make mistakes and write code that is prone to bugs and security vulnerabilities. This demonstration is just a proof of concept and a way to show the power of AI-assisted development.&lt;/p&gt;

&lt;p&gt;When building production applications, make sure you review all the AI generated code and make sure it's secure, performant and reliable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Ready to Build Along?
&lt;/h3&gt;

&lt;p&gt;To follow along with this tutorial, use our pre-configured template that includes all the necessary tools and configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/ai-assisted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template includes pre-configured MCP servers, Cursor rules, and everything you need to follow along with the tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before starting to code, there are a few tools that we need to install and configure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shuttle CLI&lt;/strong&gt;: To interact with shuttle using the CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLx CLI&lt;/strong&gt;: To interact with the database using the CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shuttle MCP Server&lt;/strong&gt;: This is a MCP server that allows AI agents to look up the latest Shuttle documentation and gives proper context on how to interact with the Shuttle platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;: To run a local database in a container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Install the Shuttle CLI
&lt;/h4&gt;

&lt;p&gt;Install the shuttle CLI, for Linux/macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://www.shuttle.dev/install | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;iwr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://www.shuttle.dev/install-win&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Install the SQLx CLI
&lt;/h4&gt;

&lt;p&gt;Install the SQLx CLI, for Postgres only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For postgres Only&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;sqlx-cli &lt;span class="nt"&gt;--no-default-features&lt;/span&gt; &lt;span class="nt"&gt;--features&lt;/span&gt; native-tls,postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setting Up MCP Servers
&lt;/h3&gt;

&lt;p&gt;Before we start, let's set up a few MCP servers that are going to help us build and deploy our API much easier.&lt;/p&gt;

&lt;h4&gt;
  
  
  Shuttle MCP Server
&lt;/h4&gt;

&lt;p&gt;The Shuttle MCP server enables AI agents to understand and execute Shuttle commands directly, including &lt;strong&gt;deployments&lt;/strong&gt;, &lt;strong&gt;fetching logs&lt;/strong&gt;, &lt;strong&gt;searching through the latest Shuttle documentation&lt;/strong&gt; and &lt;strong&gt;more&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure Cursor for Shuttle MCP Server
&lt;/h4&gt;

&lt;p&gt;Add the Shuttle MCP server to your Cursor configuration. On Linux/macOS, edit &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;. On Windows, edit &lt;code&gt;%APPDATA%\\Cursor\\mcp.json&lt;/code&gt;. Add the following to your MCP servers configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Shuttle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shuttle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementation Steps
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 1: Project Initialization
&lt;/h4&gt;

&lt;p&gt;First, we'll need to be logged in to the Shuttle CLI, you can do this by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will redirect you to the Shuttle login page, and redirect you back to the terminal after you've logged in.&lt;/p&gt;

&lt;p&gt;Next, let's create a new project for our task management API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjhyu7frjh2knofxu53o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjhyu7frjh2knofxu53o.png" alt="Shuttle CLI output showing project initialization with Axum template" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We just created a new project with the following options selected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project name&lt;/strong&gt;: &lt;code&gt;task-management-api&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory&lt;/strong&gt;: &lt;code&gt;~/Desktop/task-management-api&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template and Framework&lt;/strong&gt;: A hello world template for &lt;strong&gt;Axum&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a project on Shuttle&lt;/strong&gt;: Yes (this creates a new project on the Shuttle platform)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 2: Writing Tasks for AI Agents
&lt;/h4&gt;

&lt;p&gt;For our AI assistant to understand our intention on what we're trying to build, we'll need to write a &lt;code&gt;todo.md&lt;/code&gt; file that contains all the necessary tasks and requirements that we want to build.&lt;/p&gt;

&lt;p&gt;Now, let's write down the tasks that we want to build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Task Management API&lt;/span&gt;

This API will be a simple task management API, it will have a few endpoints, we'll use &lt;span class="gs"&gt;**Axum**&lt;/span&gt;, &lt;span class="gs"&gt;**Shuttle**&lt;/span&gt; and &lt;span class="gs"&gt;**SQLx**&lt;/span&gt; to build the API.
&lt;span class="p"&gt;
-&lt;/span&gt; GET /tasks - List all tasks with optional filtering
&lt;span class="p"&gt;-&lt;/span&gt; POST /tasks - Create a new task
&lt;span class="p"&gt;-&lt;/span&gt; GET /tasks/{id} - Get a specific task
&lt;span class="p"&gt;-&lt;/span&gt; PUT /tasks/{id} - Update a task
&lt;span class="p"&gt;-&lt;/span&gt; DELETE /tasks/{id} - Delete a task

&lt;span class="gu"&gt;## Tasks&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [ ] Install the dependencies
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Update the code to use a shared database using shuttle
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Create a migrations file for the tasks table
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Run the migrations
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Create the routes for the tasks endpoints
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Commit the changes
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Deploy the application to shuttle
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Write some tests for the production API
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Run the tests and fix the errors if any
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: AI-Assisted Development
&lt;/h4&gt;

&lt;p&gt;Let's tell Cursor to read the &lt;code&gt;todo.md&lt;/code&gt; file and work through the tasks sequentially.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4pj7ac3wir4s2omvkes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4pj7ac3wir4s2omvkes.png" alt="AI assistant using Shuttle MCP server to search documentation" width="728" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The todo list system is a great approach to give the AI agent a clear understanding of what we're trying to build, giving the AI agent a clear context of the details about the project.&lt;/p&gt;

&lt;p&gt;The AI agent immediately ran the tool &lt;code&gt;search_docs&lt;/code&gt; to search through the latest Shuttle documentation and find the relevant information needed for the the tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhq4cpue2c3sdhqucnpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhq4cpue2c3sdhqucnpo.png" alt="AI agent creating PostgreSQL migration files for tasks table" width="719" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The required migrations for the tasks have been created.&lt;/p&gt;

&lt;p&gt;Once the tasks are complete, the AI agent will commit the changes listed in &lt;code&gt;todo.md&lt;/code&gt; and deploy the application to Shuttle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1blwi7a02gvvpyln75e9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1blwi7a02gvvpyln75e9.png" alt="AI agent deploying application to Shuttle using MCP server" width="726" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI agent then proceeds to the next step which is writing the tests to make sure the API is working as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndhbks76mad4zl4u0321.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndhbks76mad4zl4u0321.png" alt="AI agent generating comprehensive tests for the API endpoints" width="720" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When running the tests, we can see that all the tests are failing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz80f5at7r5hl520jh0h9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz80f5at7r5hl520jh0h9.png" alt="Terminal output showing failing test results" width="716" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI agent begins by debugging, gathering all the details it needs to fix the failing tests. It can also call the &lt;code&gt;deployment_status&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt; tools to inspect the latest deployment, scan the logs, and pinpoint the root cause.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgk19h9uq3pbpiqgd9h7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgk19h9uq3pbpiqgd9h7.png" alt="AI agent using Shuttle MCP server to fetch deployment logs for debugging" width="726" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These two tool calls provide the AI agent with everything it needs. After reviewing the results, it understands the issue and fixes the failing tests.&lt;/p&gt;

&lt;p&gt;The issue was that &lt;code&gt;axum@0.8&lt;/code&gt; changed its path parameter syntax from &lt;code&gt;/:id&lt;/code&gt; to &lt;code&gt;/{id}&lt;/code&gt; format.&lt;/p&gt;

&lt;p&gt;The AI agent then proceeds to fix the tests and run them again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj2hvrrobhjl6g4gyf87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftj2hvrrobhjl6g4gyf87.png" alt="AI agent fixing path parameter syntax for Axum 0.8" width="725" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running the tests again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmypiru3lm50am4ngvhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmypiru3lm50am4ngvhx.png" alt="Terminal output showing all tests passing successfully" width="724" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect, the tests are now passing, and the API is working as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of AI Agents
&lt;/h2&gt;

&lt;p&gt;What we just accomplished is remarkable - we built, deployed, and tested a complete API with a single prompt. The AI agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created database migrations&lt;/li&gt;
&lt;li&gt;Implemented CRUD endpoints&lt;/li&gt;
&lt;li&gt;Deployed to production&lt;/li&gt;
&lt;li&gt;Wrote comprehensive tests&lt;/li&gt;
&lt;li&gt;Debugged and fixed issues&lt;/li&gt;
&lt;li&gt;All autonomously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This represents a fundamental shift in how we approach software development. What traditionally took hours or days of manual work was completed in minutes through intelligent automation.&lt;/p&gt;

&lt;p&gt;Before we celebrate, remember: AI-generated code is far from infallible. The automation is impressive, but it introduces real risks that demand our attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precautions and Security Risks
&lt;/h2&gt;

&lt;p&gt;AI-assisted development can supercharge productivity, yet it also opens new security fronts developers can't afford to ignore.&lt;/p&gt;

&lt;h3&gt;
  
  
  Critical Security Awareness
&lt;/h3&gt;

&lt;p&gt;AI-generated code can contain security vulnerabilities and implementation flaws that may not be immediately obvious. &lt;strong&gt;AI can be confidently wrong&lt;/strong&gt; about security implementations, error handling, business logic, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never blindly trust AI-generated code.&lt;/strong&gt; What appears correct at first glance may hide subtle, critical security flaws, or overlook key scalability and performance implications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Security Risks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI may suggest outdated or unmaintained packages&lt;/li&gt;
&lt;li&gt;Dependencies with known vulnerabilities&lt;/li&gt;
&lt;li&gt;Packages from untrusted sources&lt;/li&gt;
&lt;li&gt;Excessive permissions or unnecessary dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices for AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;The key to successful AI-assisted development is reviewing every bit of code that the AI agent writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mandatory Code Review Process
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ALWAYS review AI-generated code before accepting it.&lt;/strong&gt; This is non-negotiable for production applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing AI-Generated Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write additional tests&lt;/strong&gt; beyond what AI generates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on edge cases&lt;/strong&gt; and error conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test security boundaries&lt;/strong&gt; with invalid inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perform integration testing&lt;/strong&gt; with real data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load test&lt;/strong&gt; critical endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember: AI may speed up development, but it doesn't guarantee secure or reliable code. It's up to you to review each change and ensure everything is secure, performant, and dependable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Just a few years ago, Rust was notorious for its steep learning curve. The borrow-checker barked at nearly every line. Today, AI-powered editors like Cursor, paired with deploy-anywhere platforms such as Shuttle, make writing and shipping a Rust API almost effortless.&lt;/p&gt;

&lt;p&gt;This is what a ridiculously productive workflow looks like. We spun up a complete Rust CRUD API from scratch, shipped it, debugged it, and even hammered the production deployment with tests. And it only took us a few minutes! This isn't a dream, it's just the reality of building with AI agents on Shuttle.&lt;/p&gt;

&lt;p&gt;It's also important to note that while AI can be extremely helpful in accelerating development time, it's still subject to making mistakes and leaving security vulnerabilities in your code. It is always best practice to review the code and ensure you are only committing high-quality, secure, and performant code.&lt;/p&gt;

&lt;p&gt;Develop the mentality of treating AI as a tool, not a replacement for human developers, this way you'll get work done much faster and much more efficiently without exposing your code to security vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't just build. Create at the speed of thought&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ready to experience a true 10x developer workflow? Our AI-assisted Rust template is your launchpad into the future of software development. See what you can create when AI is your copilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--from&lt;/span&gt; shuttle-hq/shuttle-examples &lt;span class="nt"&gt;--subfolder&lt;/span&gt; axum/ai-assisted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>rust</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Hidden Costs of Deploying Rust Microservices</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Tue, 29 Jul 2025 12:14:42 +0000</pubDate>
      <link>https://dev.to/shuttle/the-hidden-costs-of-deploying-rust-microservices-4mj3</link>
      <guid>https://dev.to/shuttle/the-hidden-costs-of-deploying-rust-microservices-4mj3</guid>
      <description>&lt;p&gt;Microservices promise speed, scale, and team autonomy. But once you deploy more than a handful, the complexity creeps in. Teams suddenly face config sprawl, CI duplication, secret management, and fractured environments.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore the hidden operational costs of microservices, especially for Rust developers. You’ll see how Shuttle’s dev-first approach simplifies the provisioning and deployment journey, and we’ll even give you a real microservices challenge to try yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want to try it yourself?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've built &lt;strong&gt;ShellCon&lt;/strong&gt;, a real-world Rust microservices onboarding challenge where you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debug and deploy 3 broken services (Axum + SQLx + async Rust)&lt;/li&gt;
&lt;li&gt;Connect them to a frontend that only works when everything is fixed&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Shuttle&lt;/strong&gt; to provision, deploy, and manage the full stack: no YAML or Kubernetes needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 Perfect for Rust devs who want to learn by doing.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/shuttle-hq/shuttle-shellcon" rel="noopener noreferrer"&gt;Start the ShellCon Challenge&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll end the blog post with a special challenge for Rust developers for building Rust microservices at scale with ease. The challenge is designed to be fun and at the same time demonstrate microservice provisioning and deploying multiple microservices while using best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monolith vs Microservices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a monolith?
&lt;/h3&gt;

&lt;p&gt;Modern applications often evolve from monoliths to microservices, and for good reason.&lt;br&gt;
A &lt;strong&gt;monolith&lt;/strong&gt; is a single, tightly coupled codebase running as one process. It’s simple to start but becomes harder to scale as teams and features grow. Every small change risks affecting the entire system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microservices&lt;/strong&gt;, by contrast, are small, independently deployable services. They offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster development:&lt;/strong&gt; Teams can ship features without waiting on others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable infrastructure&lt;/strong&gt;: Only the services that need more resources get scaled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech flexibility&lt;/strong&gt;: Use Rust for performance-critical services, and Python or Go where it fits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience&lt;/strong&gt;: One service can fail without taking the whole app down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team autonomy&lt;/strong&gt;: Squads own, deploy, and monitor their own services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-effective scaling&lt;/strong&gt;: Scale only the services that need resources, not the entire application stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example: A Netflix clone might break into services like Recommendations, Streaming, Billing, and Users. With a monolith, they share the same release cycle. With microservices, each evolves and scales independently.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllr56w2j57xvjml4tqt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllr56w2j57xvjml4tqt7.png" alt="Diagram of a typical microservices architecture with independently scalable services like billing, user, and recommendation services" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microservices give you the ability to scale only the services that need the additional resources.&lt;/p&gt;

&lt;p&gt;But while microservices sound ideal, they introduce operational complexity, especially as your system grows.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hidden Costs of Deploying Rust Microservices
&lt;/h2&gt;

&lt;p&gt;While these benefits are compelling, microservices aren't without challenges. Behind these benefits are unexpected problems that can slow you down, add extra work, and confuse your team. Managing and deploying many small services often gets messy, takes more time, and brings surprises you did not plan for.&lt;/p&gt;

&lt;p&gt;Infrastructure management quickly becomes complex with configuration files, CI/CD pipelines, and service dependencies. The theoretical advantages of independent deployments and technology flexibility become overshadowed by infrastructure demands.&lt;/p&gt;
&lt;h3&gt;
  
  
  Common pitfalls of microservices
&lt;/h3&gt;

&lt;p&gt;Let's dive into some of the common pitfalls of microservices. By the end of this blog post, we'll have a good understanding of the challenges and we'll talk about &lt;strong&gt;tools that can help us overcome these challenges&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Infrastructure as Code Complexity
&lt;/h3&gt;

&lt;p&gt;Managing infrastructure with tools like &lt;strong&gt;Terraform&lt;/strong&gt; becomes challenging when scaling to dozens of configuration files across multiple environments. Each service requires its own configuration files for networking, security, compute resources, and storage.&lt;/p&gt;
&lt;h3&gt;
  
  
  CI/CD Pipeline Proliferation
&lt;/h3&gt;

&lt;p&gt;Each microservice typically requires its own CI/CD pipeline, leading to increased maintenance overhead. Teams must dedicate resources to maintaining build configurations across numerous services.&lt;/p&gt;
&lt;h3&gt;
  
  
  Observability Challenges
&lt;/h3&gt;

&lt;p&gt;Implementing a well set up monitoring system requires integrating &lt;strong&gt;logging&lt;/strong&gt;, &lt;strong&gt;metrics&lt;/strong&gt;, and &lt;strong&gt;tracing&lt;/strong&gt; across all services. Organizations frequently end up with disconnected monitoring tools, making incident investigation difficult when problems occur.&lt;/p&gt;
&lt;h3&gt;
  
  
  Domains and Certificate Management
&lt;/h3&gt;

&lt;p&gt;Managing domains and SSL certificates across multiple microservices creates significant operational overhead. Each service and environment typically requires its own subdomain (&lt;strong&gt;api.company.com&lt;/strong&gt;, &lt;strong&gt;auth.company.com&lt;/strong&gt;, &lt;strong&gt;payments.company.com&lt;/strong&gt;), and each subdomain needs SSL certificates for secure communication. They also need to be renewed periodically. Without proper tooling, this becomes extremely difficult to manage, with surprises of expired certificates.&lt;/p&gt;
&lt;h3&gt;
  
  
  Environment Management Complexity
&lt;/h3&gt;

&lt;p&gt;Microservices multiply environment management challenges. Where a monolith might have three environments (&lt;strong&gt;development&lt;/strong&gt;, &lt;strong&gt;staging&lt;/strong&gt;, &lt;strong&gt;production&lt;/strong&gt;), microservices often require environment parity across dozens of services.&lt;/p&gt;

&lt;p&gt;Each service needs its own environment-specific configurations, database connections, and service discovery settings. Maintaining consistency across environments becomes extremely difficult when services have different deployment schedules and dependency requirements.&lt;/p&gt;
&lt;h3&gt;
  
  
  Database Proliferation and Management
&lt;/h3&gt;

&lt;p&gt;Each microservice typically requires its own database to maintain data isolation and independence. This database-per-service pattern creates operational complexity that teams often underestimate.&lt;/p&gt;

&lt;p&gt;Database provisioning, backup strategies, monitoring, and maintenance must be replicated across dozens of database instances. Each database needs its own connection pooling, and performance tuning.&lt;/p&gt;
&lt;h3&gt;
  
  
  Secrets and Configuration
&lt;/h3&gt;

&lt;p&gt;The more services you have, the more secrets and configurations you need to manage. Without proper tooling for managing secrets and configurations, this becomes extremely difficult to manage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ready to experience these challenges for yourself?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve built an interactive onboarding challenge using &lt;strong&gt;Rust&lt;/strong&gt; and &lt;strong&gt;Shuttle&lt;/strong&gt; called &lt;strong&gt;ShellCon&lt;/strong&gt;. You’ll debug and optimize 3 microservices to make the frontend app work,  including async Rust code, Axum HTTP routes, and SQL queries.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/shuttle-hq/shuttle-shellcon" rel="noopener noreferrer"&gt;Start the ShellCon Challenge&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  How Microservices Are Typically Deployed
&lt;/h2&gt;

&lt;p&gt;In conventional microservices architectures, each service typically requires its own microservice deployment pipeline and infrastructure configuration. Let's examine what this traditionally looks like and understand the complexity involved.&lt;/p&gt;
&lt;h3&gt;
  
  
  Docker and Container Management
&lt;/h3&gt;

&lt;p&gt;Most microservice deployments start with Docker. Each service needs its own &lt;code&gt;Dockerfile&lt;/code&gt; and typically a &lt;code&gt;docker-compose.yml&lt;/code&gt; file for local development. Here's an example of what a traditional setup might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;blog-api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./blog-service&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://user:password@postgres:5432/blog_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ANALYTICS_SERVICE_URL=http://analytics-service:3000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ANALYTICS_API_KEY=your-super-secret-api-key-here&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;analytics-service&lt;/span&gt;

  &lt;span class="na"&gt;analytics-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./analytics-service&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3002:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://user:password@postgres:5432/analytics_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ANALYTICS_API_KEY=your-super-secret-api-key-here&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=blog_db&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service will have its own &lt;code&gt;Dockerfile&lt;/code&gt; as well. Managing these needs careful attention - you'll need to make sure that the services are compatible with each other, and that the services are compatible with the infrastructure you're using.&lt;/p&gt;

&lt;p&gt;This is for a simple setup, however, when it comes to production environments with high traffic, you'll need multi node deployments and things get complex quickly, and you'll need to manage a lot of additional complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes manifests&lt;/li&gt;
&lt;li&gt;Load balancer configurations with SSL termination&lt;/li&gt;
&lt;li&gt;Service meshes like Istio&lt;/li&gt;
&lt;li&gt;Auto-scaling groups&lt;/li&gt;
&lt;li&gt;Network security&lt;/li&gt;
&lt;li&gt;Database clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list goes on, and the complexity increases.&lt;/p&gt;

&lt;p&gt;Secret management also becomes extremely complex with rotation strategies, encrypted storage systems like Vault, access control policies, and certificate management across all environments.&lt;/p&gt;

&lt;p&gt;You'll also need comprehensive monitoring and observability, once you have multiple containers across multiple nodes, this becomes extremely difficult to manage.&lt;/p&gt;

&lt;p&gt;This traditional approach, while powerful and battle-tested, creates significant operational overhead where teams often spend most of their time managing infrastructure, Kubernetes configurations, and deployment pipelines instead of building features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplify Rust Microservice Deployment with Shuttle
&lt;/h2&gt;

&lt;p&gt;Luckily, we're not going to have to do all of that. With &lt;strong&gt;Shuttle&lt;/strong&gt;, we can focus on building our application logic instead of wrestling with infrastructure complexity. Shuttle handles microservice provisioning, secret management, and deployment orchestration automatically, letting us deploy production-ready Rust microservices with just a few commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing a Developer-First Approach to Infrastructure
&lt;/h3&gt;

&lt;p&gt;Instead of managing infrastructure separately, we can manage it directly in our application code, using the same tools and languages we already know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shuttle&lt;/strong&gt; embodies this approach by removing the complexity of managing infrastructure and deployment. Rather than dealing with Terraform or cloud consoles, you define resources like &lt;strong&gt;databases and secrets&lt;/strong&gt; using straightforward Rust attributes.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build multiple interconnected Rust microservices with &lt;strong&gt;Axum&lt;/strong&gt; and then deploy them to the cloud with &lt;strong&gt;Shuttle&lt;/strong&gt;. We'll create a &lt;strong&gt;Blog API service&lt;/strong&gt; and an &lt;strong&gt;Analytics service&lt;/strong&gt; that communicate with each other, demonstrating true microservices architecture with service-to-service communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Multiple Microservices with Shuttle
&lt;/h3&gt;

&lt;p&gt;For this example, we'll build two interconnected services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Analytics Service&lt;/strong&gt; - Tracks and processes blog post views and interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog API Service&lt;/strong&gt; - Manages blog posts and sends analytics events&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start by installing the &lt;strong&gt;Shuttle CLI&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux and macOS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSfL&lt;/span&gt; https://www.shuttle.dev/install | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows (PowerShell)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iwr https://www.shuttle.dev/install-win | iex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login to the &lt;strong&gt;Shuttle CLI&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install the sqlx-cli
&lt;/h3&gt;

&lt;p&gt;To be able to use &lt;code&gt;sqlx&lt;/code&gt; and interact with the database, we'll need to install the &lt;code&gt;sqlx-cli&lt;/code&gt; tool. This particular command sets it up for postgres only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install sqlx-cli (if not already installed)&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;sqlx-cli &lt;span class="nt"&gt;--no-default-features&lt;/span&gt; &lt;span class="nt"&gt;--features&lt;/span&gt; native-tls,postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;The complete source code for the microservices we'll build is available on GitHub: &lt;a href="https://github.com/dcodesdev/shuttle-microservice-demo" rel="noopener noreferrer"&gt;Shuttle Microservices Demo&lt;/a&gt; You can clone the repository to follow along or reference the final implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Analytics Service
&lt;/h3&gt;

&lt;p&gt;Create the first project for our analytics service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--template&lt;/span&gt; axum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewrfbzzrc14gk7ybunia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewrfbzzrc14gk7ybunia.png" alt="Shuttle CLI terminal output showing initialization of a Rust microservice using the Axum template" width="718" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add dependencies for the analytics service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo add shuttle-shared-db serde sqlx serde_json &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; shuttle-shared-db/postgres,shuttle-shared-db/sqlx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; serde/derive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use &lt;code&gt;sqlx&lt;/code&gt; properly to interact with the database, we'll need to set up a local database and set the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable in the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To create a database, the easiest way to do it is by using Docker, you'll need to have docker installed on your machine, if you haven't already, make sure you &lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;install it here&lt;/a&gt; for your specific operating system.&lt;/p&gt;

&lt;p&gt;Once you have docker installed, you can run the following command to create a database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; postgres &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your database is ready to accept connections, you can set up the analytics database:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpi3lvzkxif6lmel76sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpi3lvzkxif6lmel76sw.png" alt="Terminal showing SQLx CLI creating a PostgreSQL database for a Rust analytics microservice" width="738" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- migrations/&amp;lt;timestamp&amp;gt;_create_events.sql&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sqlx migrate run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Securing service communication
&lt;/h3&gt;

&lt;p&gt;In order to secure the communication between the analytics service and the blog service, we'll need a way to verify the identity of the requester. To do that, we'll have a &lt;strong&gt;secret key&lt;/strong&gt; that only the two services know about. If the requester has the correct secret key, the request will be allowed to proceed, otherwise it will be rejected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secret management with Shuttle
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;secret key&lt;/strong&gt; must be kept secret, therefore it &lt;strong&gt;must not&lt;/strong&gt; be committed to version control and must be handled securely using proper tooling. For that, Shuttle provides us a way to define secrets in a secure manner, by giving us a macro that we can use directly in our application code. To define the secrets, &lt;strong&gt;Shuttle&lt;/strong&gt; expects a &lt;code&gt;Secrets.toml&lt;/code&gt; file in the root of the project.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Secrets.toml&lt;/code&gt; file is a simple configuration file that contains the secrets for the project. It's a &lt;strong&gt;TOML&lt;/strong&gt; file, which is a simple configuration file format that is easy to read and write.&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;Secrets.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Secrets.toml&lt;/span&gt;
&lt;span class="py"&gt;ANALYTICS_API_KEY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-super-secret-api-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the &lt;code&gt;ANALYTICS_API_KEY&lt;/code&gt; secret in the code with the &lt;code&gt;#[shuttle_runtime::Secrets]&lt;/code&gt; macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nd"&gt;#[shuttle_runtime::Secrets]&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ANALYTICS_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ANALYTICS_API_KEY must be set"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Use the api_key anywhere in your code...&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database provisioning with Shuttle
&lt;/h3&gt;

&lt;p&gt;Shuttle provides an easy way to provision databases, just by adding a macro to your &lt;code&gt;main.rs&lt;/code&gt; file, you'll have a database set up and connected to after you deploy to Shuttle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_shared_db::Postgres]&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make development easier, you can set your own &lt;code&gt;local_uri&lt;/code&gt; to connect to your local database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_shared_db::Postgres(&lt;/span&gt;
        &lt;span class="nd"&gt;local_uri&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postgres://postgres:password@localhost:5432/analytics_db"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's implement the routes for the service, we'll have two routes, one for receiving events and one for getting statistics.&lt;/p&gt;

&lt;p&gt;For the full version of the code, see the &lt;a href="https://github.com/dcodesdev/shuttle-microservice-demo" rel="noopener noreferrer"&gt;GitHub repository here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;receive_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AnalyticsEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"INSERT INTO events (event_type, post_id, data) VALUES ($1, $2, $3)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.post_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.data&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EventStats&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query_as!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;EventStats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"SELECT event_type, COUNT(*) as count FROM events GROUP BY event_type"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.fetch_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having a look at this snippet in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[shuttle_runtime::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_shared_db::Postgres(&lt;/span&gt;
        &lt;span class="nd"&gt;local_uri&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postgres://postgres:password@localhost:5432/analytics_db"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PgPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[shuttle_runtime::Secrets]&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;shuttle_axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ShuttleAxum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connecting to a database using Shuttle is as simple as adding a macro to your &lt;code&gt;main.rs&lt;/code&gt; file. This macro will automatically provision a database for you. The &lt;code&gt;local_uri&lt;/code&gt; is used to connect to your local database for development purposes.&lt;/p&gt;

&lt;p&gt;For managing secrets, we have used the &lt;code&gt;#[shuttle_runtime::Secrets]&lt;/code&gt; attribute provided by Shuttle that will automatically load the secrets from the &lt;code&gt;Secrets.toml&lt;/code&gt;. Shuttle will automatically push the secrets to the cloud in a secure manner when running &lt;code&gt;shuttle deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s build a middleware function to authenticate requests using the &lt;code&gt;ANALYTICS_API_KEY&lt;/code&gt; from the &lt;code&gt;Secrets.toml&lt;/code&gt; file. This way, only our blog service can send analytics events to the analytics service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;auth_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer "&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.api_key&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploying the Analytics Service
&lt;/h3&gt;

&lt;p&gt;Before we deploy the service, we'll need to follow another step that is specific to &lt;a href="https://github.com/launchbadge/sqlx/" rel="noopener noreferrer"&gt;sqlx&lt;/a&gt;, we'll need to prepare the SQL queries for production so that the code can compile without the need for an active database connection, you can read more about &lt;a href="https://github.com/launchbadge/sqlx/" rel="noopener noreferrer"&gt;how sqlx works here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare the SQL queries for production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sqlx prepare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you commit the generated metadata by SQLx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Commit the generated metadata by SQLx&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Prepare SQL queries for production"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy the service
&lt;/h3&gt;

&lt;p&gt;Deploying services with &lt;strong&gt;Shuttle&lt;/strong&gt; is as simple as running &lt;code&gt;shuttle deploy&lt;/code&gt; in the root of the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will deploy the service to the cloud, you'll see the deployed URL in the console, we'll use this URL in our blog service to send analytics events to the analytics service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7jggovbd7590rchwv2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7jggovbd7590rchwv2m.png" alt="Terminal output showing successful deployment of the analytics microservice to the Shuttle cloud platform" width="619" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Blog API Service
&lt;/h3&gt;

&lt;p&gt;Now let's create our blog service that will depend on the analytics service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle init &lt;span class="nt"&gt;--template&lt;/span&gt; axum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the required dependencies for our blog service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo add shuttle-shared-db serde reqwest tokio sqlx serde_json &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; shuttle-shared-db/postgres,shuttle-shared-db/sqlx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; serde/derive &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; reqwest/json &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--features&lt;/span&gt; tokio/full
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up the database environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create .env file for local development&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE_URL=postgres://postgres:password@localhost:5432/blog_db"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env

&lt;span class="c"&gt;# Create the database&lt;/span&gt;
cargo sqlx database create

&lt;span class="c"&gt;# Create migration&lt;/span&gt;
cargo sqlx migrate add create_posts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the migration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- migrations/&amp;lt;timestamp&amp;gt;_create_posts.sql&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sqlx migrate run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;Secrets.toml&lt;/code&gt; file for configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Secrets.toml&lt;/span&gt;
&lt;span class="py"&gt;ANALYTICS_SERVICE_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://analytics-service-m7iz.shuttle.app"&lt;/span&gt; &lt;span class="c"&gt;# Replace with your analytics service URL&lt;/span&gt;
&lt;span class="py"&gt;ANALYTICS_API_KEY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-super-secret-api-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's implement our blog service with analytics tracking. We’re going to implement a few routes: Create a post, get a post and get a list of all posts. See the &lt;a href="https://github.com/dcodesdev/shuttle-microservice-demo" rel="noopener noreferrer"&gt;source code here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;create_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CreatePost&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Service to service secure communication
&lt;/h3&gt;

&lt;p&gt;To send events to the analytics services, we’ll need to create a new function to send analytics events to the analytics service, this function is used to communicate with the analytics service using secure &lt;code&gt;HTTP&lt;/code&gt; requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;send_analytics_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AnalyticsEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.analytics_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bearer {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.analytics_api_key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have used the &lt;code&gt;ANALYTICS_SERVICE_URL&lt;/code&gt; and &lt;code&gt;ANALYTICS_API_KEY&lt;/code&gt; from the &lt;code&gt;Secrets.toml&lt;/code&gt; file to send the analytics event to the analytics service. This way both services are independent of each other, they have their own databases and can be deployed independently.&lt;/p&gt;

&lt;p&gt;To make this communication secure, we have used the &lt;code&gt;ANALYTICS_API_KEY&lt;/code&gt; to authenticate the request to the analytics service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the blog service
&lt;/h3&gt;

&lt;p&gt;The process is similar to the analytics service, we'll need to prepare the SQL queries for production and deploy the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sqlx prepare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit the generated metadata by SQLx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Prepare SQL queries for production"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy the service using &lt;strong&gt;Shuttle&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shuttle deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld9ajhc7eq8fcelsqipx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld9ajhc7eq8fcelsqipx.png" alt="Terminal showing Shuttle deployment output with the live URL of the Blog API microservice" width="512" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎉 Great! Now both of our services are deployed and ready to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the services
&lt;/h3&gt;

&lt;p&gt;Let's test the services and make sure they're working as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a blog post (this will trigger an analytics event)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://blog-service-3hhs.shuttle.app/posts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title": "My First Post", "content": "Hello microservices!"}'&lt;/span&gt;

&lt;span class="c"&gt;# Response&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"title"&lt;/span&gt;:&lt;span class="s2"&gt;"My First Post"&lt;/span&gt;,&lt;span class="s2"&gt;"content"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello microservices!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# View the post (this will trigger another analytics event)&lt;/span&gt;
curl https://blog-service-3hhs.shuttle.app/posts/1

&lt;span class="c"&gt;# Response&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;:1,&lt;span class="s2"&gt;"title"&lt;/span&gt;:&lt;span class="s2"&gt;"My First Post"&lt;/span&gt;,&lt;span class="s2"&gt;"content"&lt;/span&gt;:&lt;span class="s2"&gt;"Hello microservices!"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Check analytics stats&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://analytics-service-m7iz.shuttle.app/stats | jq
&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"event_type"&lt;/span&gt;: &lt;span class="s2"&gt;"post_viewed"&lt;/span&gt;,
    &lt;span class="s2"&gt;"count"&lt;/span&gt;: 2
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"event_type"&lt;/span&gt;: &lt;span class="s2"&gt;"post_created"&lt;/span&gt;,
    &lt;span class="s2"&gt;"count"&lt;/span&gt;: 1
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Our services are working as expected, we have a blog service that can create and view posts, and an analytics service that can track analytics events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional vs Shuttle: Microservice Deployment Complexity Comparison
&lt;/h3&gt;

&lt;p&gt;Comparing this approach with the traditional approach, we can see that we no longer need to manage multiple configuration files and orchestration tools - we can focus on building our application logic instead. With this approach, teams can focus on building smaller Rust microservices without worrying about the infrastructure, secrets management, and observability tools. This makes microservice deployment much faster and easier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Traditional Setup (Docker/Kubernetes/Terraform)&lt;/th&gt;
&lt;th&gt;Shuttle Setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual configuration of Dockerfiles, docker-compose.yml, Kubernetes manifests, and Terraform files&lt;/td&gt;
&lt;td&gt;Automated infrastructure provisioning through Shuttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Provisioning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual setup of PostgreSQL containers, volumes, and Terraform database resources&lt;/td&gt;
&lt;td&gt;Automatic database provisioning with Shuttle's shared database feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secret Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Environment variables in docker-compose.yml, Kubernetes secrets, or external tools like Vault&lt;/td&gt;
&lt;td&gt;Integrated secret management using Shuttle's Secrets.toml&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires manual deployment steps, CI/CD pipeline configuration, and Kubernetes/Terraform orchestration&lt;/td&gt;
&lt;td&gt;Simple deployment with &lt;code&gt;shuttle deploy&lt;/code&gt; command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual configuration of Kubernetes HPA, scaling policies, and load balancers&lt;/td&gt;
&lt;td&gt;Built-in scalability features with Shuttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires additional monitoring tools, Prometheus setup, and configuration&lt;/td&gt;
&lt;td&gt;Integrated monitoring capabilities with Shuttle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development Experience&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complex setup for local development with Docker, minikube, or kind clusters&lt;/td&gt;
&lt;td&gt;Simplified development experience with Shuttle's local development features&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Ready to Try It Yourself? Build Microservices with Rust &amp;amp; Shuttle
&lt;/h2&gt;

&lt;p&gt;Now that you have everything you need to build Rust microservices with &lt;strong&gt;Shuttle&lt;/strong&gt;, &lt;strong&gt;we have a challenge for you&lt;/strong&gt;, in this challenge we put everything we learned together to build a simple microservices architecture application.&lt;/p&gt;

&lt;p&gt;The challenge is designed to be fun and at the same time improve your skills and knowledge of building Rust microservices.&lt;/p&gt;

&lt;p&gt;The challenge is available on &lt;a href="https://github.com/shuttle-hq/shuttle-shellcon" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, you can clone the repository and follow the instructions to complete the challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Microservices provide a variety of benefits - isolating different parts of your application makes deployment easier, enhances security and scalability. This way you can scale only the services that need the additional resources.&lt;/p&gt;

&lt;p&gt;However, this doesn't come without a cost. Adding more microservices means more complexity and infrastructure management, which makes development slower and more difficult.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;Shuttle&lt;/strong&gt; can make microservice deployment easier. Without having to deal with the complexity of managing infrastructure, you can focus on building your application. You no longer have to worry about microservice provisioning, managing secrets, or managing your CI/CD pipeline. Learn more about &lt;a href="https://docs.shuttle.dev/getting-started/quick-start" rel="noopener noreferrer"&gt;building Rust web applications with Shuttle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This makes development easier and much faster, putting you ahead of the curve. You can focus on &lt;strong&gt;shipping&lt;/strong&gt; rather than &lt;strong&gt;managing infrastructure&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>infrastructureascode</category>
      <category>devops</category>
      <category>cloud</category>
      <category>rust</category>
    </item>
    <item>
      <title>Choosing the Right Rust Web Framework: An Overview</title>
      <dc:creator>Shuttle</dc:creator>
      <pubDate>Wed, 23 Aug 2023 13:44:22 +0000</pubDate>
      <link>https://dev.to/shuttle/choosing-the-right-rust-web-framework-an-overview-19nf</link>
      <guid>https://dev.to/shuttle/choosing-the-right-rust-web-framework-an-overview-19nf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Author: Stefan Baumgartner; Owner at Oida.dev; Rust Linz Organizer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the dynamic landscape of web development, Rust has emerged as a language of choice for building safe and performant applications. As Rust's popularity grows, so does the array of web frameworks designed to harness its strengths. This article compares some of the best Rust frameworks highlighting their respective advantages and drawbacks to help you make informed decisions for your projects. It also takes a lookout on frameworks to look out for, as they might change how we build web applications in Rust.&lt;/p&gt;

&lt;p&gt;Since most of the web frameworks feel very similar in use at a first glance, the differences are much more nuanced and in detail. I hope to highlight the most important differences in text, but to give you a better idea, I also show example code with every framework that does more than a simple hello world. All the examples are taken from the respective GitHub repos.&lt;/p&gt;

&lt;p&gt;Also note that this list is by no means exhaustive, and I definitely missed some of the frameworks that are out there. If you want to have your favourite framework included, please reach out to me on &lt;a href="https://twitter.com/ddprrt" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://mastodon.social/deadparrot" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Popular Rust Frameworks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Axum
&lt;/h3&gt;

&lt;p&gt;Axum is a web application framework with a special standing in the Rust ecosystem. It is part of the &lt;a href="https://tokio.rs/" rel="noopener noreferrer"&gt;Tokio&lt;/a&gt; project, which the runtime for writing asynchronous network applications with Rust. Not only does Axum use Tokio as its asynchronous runtime, but it also integrates with other libraries from the Tokio ecosystem, making use of &lt;em&gt;Hyper&lt;/em&gt; as its HTTP server and &lt;em&gt;Tower&lt;/em&gt; for middleware. In doing so, developers are able to reuse existing libraries and tools from the Tokio ecosystem.&lt;/p&gt;

&lt;p&gt;Axum also strives to deliver a best in class developer experience without relying on macros, but rather leveraging Rust's type system to provide a safe and ergonomic API. This is achieved by using traits to define the core abstractions of the framework, such as the &lt;code&gt;Handler&lt;/code&gt; trait, which is used to define the core logic of an application. This approach allows developers to easily compose applications from smaller components, which can be reused across multiple applications.&lt;/p&gt;

&lt;p&gt;A handler in Axum is a function that takes a request and returns a response. This is similar to other backend frameworks, but with Axum's &lt;code&gt;FromRequest&lt;/code&gt; trait, developers can specify the types of data that should be extracted from the request. The return type needs to implement the &lt;code&gt;IntoResponse&lt;/code&gt; trait, and there are already a number of types that implement this trait, including tuple types that allow to easily change e.g. the status code of a response.&lt;/p&gt;

&lt;p&gt;Whenever you work with Rust's type system, generics and especially async methods in traits (or more concretely: a returned &lt;code&gt;Future&lt;/code&gt;), you know how complex Rust's error messages can get when you don't satisfy a trait bound. Especially when you try to match abstract trait bounds, it happens ever so often that you get a wall of text that is hard to decipher. Change the order of a few lines, and nothing works anymore! Axum provides a library with helper macros that put the error to where it actually happens, making it easier to understand what went wrong.&lt;/p&gt;

&lt;p&gt;Axum does a lot of things right, and it's very easy to get applications started that do &lt;em&gt;a lot&lt;/em&gt;. However, there are some things that you need to look out for. The version is still below 1.0, and the Axum team takes the liberty to change APIs fundamentally between versions, which can cause your apps to break big time. That's the deal with 0.x versions, we know, but some changes appear to be so subtle, yet require you to develop a different mental model of how things work underneath. If you included a &lt;code&gt;Timeout&lt;/code&gt; layer (built-in in Tower, yay!), it worked easily in one version, needed a catch-all error handler in another, and an tied error handler in the next. This is not a big deal, but it can be frustrating when you're trying to get things done or you start a project with the latest version and things suddenly work differently. Also while you are able to leverage the entire Tokio ecosystem, you sometimes need to deal with glue types and traits, rather then going to the Tokio functions directly. One example is the use of anything stream and (web) socket related.&lt;/p&gt;

&lt;p&gt;The good examples help, but you need to keep track.&lt;/p&gt;

&lt;p&gt;Nonetheless, Axum is my personal favourite, and also the framework I use for &lt;a href="https://shuttle.rs/launchpad" rel="noopener noreferrer"&gt;Shuttle Launchpad&lt;/a&gt;. I love the expressiveness and the concepts underneath, and there hasn't been a thing that I wanted to solve that I couldn't do inutitively by understanding the right concepts. If you want to get to know the Axum concepts, check out my &lt;a href="https://fettblog.eu/slides/microservices-with-rust-and-tokio/" rel="noopener noreferrer"&gt;slides from my Tokio + Microservices workshop&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Axum Example
&lt;/h4&gt;

&lt;p&gt;An abbreviated example from the &lt;a href="https://github.com/tokio-rs/axum/blob/main/examples/testing-websockets/src/main.rs" rel="noopener noreferrer"&gt;Axum repo&lt;/a&gt; showing a WebSocket handler that echos any message it receives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1:3000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"listening on {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="nf"&gt;.local_addr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// WebSocket routes can generally be tested in two ways:&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// - Integration tests where you run the server and connect with a real WebSocket client.&lt;/span&gt;
    &lt;span class="c1"&gt;// - Unit tests where you mock the socket as some generic send/receive type&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Which version you pick is up to you. Generally we recommend the integration test version&lt;/span&gt;
    &lt;span class="c1"&gt;// unless your app has a lot of setup that makes it hard to run in a test.&lt;/span&gt;
    &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/integration-testable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;integration_testable_handler&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/unit-testable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit_testable_handler&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A WebSocket handler that echos any message it receives.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// This one we'll be integration testing so it can be written in the regular way.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;integration_testable_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocketUpgrade&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="nf"&gt;.on_upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;integration_testable_handle_socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;integration_testable_handle_socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
                &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You said: {msg}"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                &lt;span class="k"&gt;.await&lt;/span&gt;
                &lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Axum in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Macro-free API.&lt;/li&gt;
&lt;li&gt;Strong ecosystem by leveraging Tokio, Tower, and Hyper.&lt;/li&gt;
&lt;li&gt;Great developer experience.&lt;/li&gt;
&lt;li&gt;Still in 0.x, so breaking changes can happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Actix
&lt;/h3&gt;

&lt;p&gt;Actix is one of Rust's web frameworks that has been around for a while, and thus is very popular. Like any good open source project it has seen many iterations, but it has reached major versions other than 0 and keeps its stability guarantees: Within a major version, you can be sure that there are no breaking changes.&lt;/p&gt;

&lt;p&gt;When we talk Actix, we specifically talk about &lt;a href="https://actix.rs" rel="noopener noreferrer"&gt;Actix Web&lt;/a&gt;, which is the web framework part of the Actix project. Actix itself is a project that provides a number of libraries for building concurrent applications, and is based on the actor model. The "Web" part of Actix abstracts lot of the actor model away, and provides a more traditional web framework API.&lt;/p&gt;

&lt;p&gt;At a first glance, Actix looks very familiar to other web frameworks in Rust. You use macros to define HTTP methods and routes (like Rocket), and you use extractors to get data from the request (like Axum). The similarities with Axum are striking, also in how they name concepts and traits. The biggest difference is that Actix does not tie itself to strong to the Tokio ecosystem. While Tokio is still the runtime underneath Actix, the framework comes with its own abstractions and traits, and also with its own ecosystem of crates. This has pros and cons. On one hand, you can be sure that things usually work well together, on the other hand you might miss out on a lot of things that are already available in the Tokio ecosystem.&lt;/p&gt;

&lt;p&gt;One thing that strikes me odd is that Actix implements its own Service trait, which is basically the same as Tower's, but still incompatible. Which means that most of the available Middleware in the Tower ecosystem is not available for Actix.&lt;/p&gt;

&lt;p&gt;What's also interesting is that if you need some special tasks in Actix that you need to implement on your own, you might get confronted with the Actor model that runs everything in the framework. This might add some layers of complexity that you might not want to deal with.&lt;/p&gt;

&lt;p&gt;But the community around Actix delivers. The framework supports HTTP/2 and Websocket upgrades, it has crates and guides for the most common tasks in a web framework, excellent (and I mean &lt;em&gt;excellent&lt;/em&gt;) documentation, and it's fast. Actix is popular for a reason, and if you need to keep version guarantees, it might be your best choice right now.&lt;/p&gt;

&lt;h4&gt;
  
  
  Actix Example
&lt;/h4&gt;

&lt;p&gt;A simple &lt;a href="https://actix.rs/docs/websockets" rel="noopener noreferrer"&gt;websocket echo server&lt;/a&gt; in Actix looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web_actors&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/// Define HTTP actor&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;MyWs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MyWs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;WebsocketContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/// Handler for ws::Message message&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ProtocolError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MyWs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ProtocolError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="nf"&gt;.pong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="nf"&gt;.binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyWs&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[actix_web::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ws/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="nf"&gt;.bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Actix in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Strong, self-contained ecosystem.&lt;/li&gt;
&lt;li&gt;Actor model based.&lt;/li&gt;
&lt;li&gt;Stable API via major version guarantees.&lt;/li&gt;
&lt;li&gt;Fantastic documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rocket
&lt;/h3&gt;

&lt;p&gt;Rocket has been the star in the Rust web framework ecosystem for quite a while, with it's unapologetic approach to developer experience, the reliance on familar and existing concepts, and its ambitious goal to provide a batteries-included experience.&lt;/p&gt;

&lt;p&gt;You can see its ambitions when you enter their beautiful &lt;a href="https://rocket.rs" rel="noopener noreferrer"&gt;website&lt;/a&gt;: Macro-based routing, built-in form handling, support for databases and state management, and its own veersion of templating! Rocket really tries to get everything done that you need to build a web application.&lt;/p&gt;

&lt;p&gt;However, Rocket's ambitions take their toll. While still being actively developed, the releases are not as frequent as they used to be. Which means that users of the framework miss out on a lot of important stuff.&lt;/p&gt;

&lt;p&gt;Also, with it's batteries-included approach, you also need to learn how Rocket does things. Rocket apps have a lifecycle, the building blocks are connected in a particular way, and if something goes wrong, you need to understand what goes wrong.&lt;/p&gt;

&lt;p&gt;Rocket is a great framework, and if you want to get started with Rust web development, it's a great choice. Personally, I have a soft spot for Rocket and I hope that development picks up. For many of us, Rocket was the first segue into Rust, and it's still fun to develop with it. Nonetheless, I usually rely on features that are not available in Rocket, and thus I don't use it in production.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rocket Example
&lt;/h4&gt;

&lt;p&gt;An abbreviated example of a Rocket application that deals with forms from their &lt;a href="https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/examples/forms/src/main.rs" rel="noopener noreferrer"&gt;example repo&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;FromForm)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;len(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;..&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;eq(self&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;second))]&lt;/span&gt;
    &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;eq(self&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;first))]&lt;/span&gt;
    &lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;FromForm)]&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Submission&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;len(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;..&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;len(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;..&lt;/span&gt;&lt;span class="nd"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;ext(ContentType::PDF))]&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TempFile&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;FromForm)]&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;len(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;..&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[field(validate&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;contains(&lt;/span&gt;&lt;span class="sc"&gt;'@'&lt;/span&gt;&lt;span class="nd"&gt;)&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;or_else(msg&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nd"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid email address"&lt;/span&gt;&lt;span class="nd"&gt;)))]&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;FromForm)]&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Submission&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'v&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[get(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// NOTE: We use `Contextual` here because we want to collect all submitted form&lt;/span&gt;
&lt;span class="c1"&gt;// fields to re-render forms with submitted values on error. If you have no such&lt;/span&gt;
&lt;span class="c1"&gt;// need, do not use `Contextual`. Use the equivalent of `Form&amp;lt;Submit&amp;lt;'_&amp;gt;&amp;gt;`.&lt;/span&gt;
&lt;span class="nd"&gt;#[post(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;data&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;form&amp;gt;"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'r&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Contextual&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'r&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"submission: {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nn"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.context&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="py"&gt;.context&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[launch]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;rocket&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;routes!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;.attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fairing&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;FileServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;relative!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/static"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Rocket in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Batteries-included approach.&lt;/li&gt;
&lt;li&gt;Great developer experience.&lt;/li&gt;
&lt;li&gt;Not as actively developed as it used to be.&lt;/li&gt;
&lt;li&gt;Still a great choice for beginners.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Experimental Rust Frameworks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Warp
&lt;/h3&gt;

&lt;p&gt;Oh, Warp! You are a beautiful, strange, and powerful beast. Warp is a web framework that is built on top of Tokio, and it's a very good one. It's also very different from the other frameworks that we have seen so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/seanmonstar/warp" rel="noopener noreferrer"&gt;Warp&lt;/a&gt; shares a few common traits (haha!) with Axum: It's built on Tokio and Hyper, and makes use of Tower middleware. However, it's very different in its approach. Warp is built on top of the &lt;code&gt;Filter&lt;/code&gt; trait.&lt;/p&gt;

&lt;p&gt;In Warp, you build a pipeline of filters that are applied to the incoming request, and the request is passed through the pipeline until it reaches the end. Filters can be chained, and they can be composed. This allows you to build very complex pipelines that are still easy to understand.&lt;/p&gt;

&lt;p&gt;Warp is also a bit closer to the Tokio ecosystem than Axum, which means that you might deal with more Tokio structs and concepts without any glue traits.&lt;/p&gt;

&lt;p&gt;Warp takes a very functional approach and if that's your style of programming you are going to love the expressiveness and composability of Warp. When you look at a piece of Warp code, it often reads like a story of what's happening, and it's fun and amazing that this works in Rust.&lt;/p&gt;

&lt;p&gt;You might want to turn off inlay hints in your Rust Analyzer setting, though. With all those different functions and filters being chained, the types in Warp get very long and very complex, and also hard to decipher. Same goes for error messages, which can be pages of text that are hard to understand.&lt;/p&gt;

&lt;p&gt;Also, while the filter concept is great once you get through, sometimes you want to have the declarative router, handler and extractor style that you get with, well, all the other frameworks.&lt;/p&gt;

&lt;p&gt;Warp is a great framework, and I love it. However, it's not the most beginner-friendly framework, and it's also not the most popular one. Which means that you might have a harder time finding help and resources. But it's fun for quick and little apps, and it's experimental style might give you new ideas!&lt;/p&gt;

&lt;h4&gt;
  
  
  Warp Example
&lt;/h4&gt;

&lt;p&gt;An abbreviated example of a websocket chat from &lt;a href="https://github.com/seanmonstar/warp/blob/master/examples/websockets_chat.rs" rel="noopener noreferrer"&gt;their example repo&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;NEXT_USER_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AtomicUsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;AtomicUsize&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cd"&gt;/// Our state of currently connected users.&lt;/span&gt;
&lt;span class="cd"&gt;///&lt;/span&gt;
&lt;span class="cd"&gt;/// - Key is their id&lt;/span&gt;
&lt;span class="cd"&gt;/// - Value is a sender of `warp::ws::Message`&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnboundedSender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Turn our "state" into a new Filter...&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// GET /chat -&amp;gt; websocket upgrade&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// The `ws()` filter will prepare Websocket handshake...&lt;/span&gt;
        &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// This will call our function if the handshake succeeds.&lt;/span&gt;
            &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="nf"&gt;.on_upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;user_connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// GET / -&amp;gt; index html&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INDEX_HTML&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="nf"&gt;.or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;warp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3030&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;user_connected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Use a counter to assign a new unique ID for this user.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NEXT_USER_ID&lt;/span&gt;&lt;span class="nf"&gt;.fetch_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Ordering&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Relaxed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new chat user: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Split the socket into a sender and receive of messages.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;user_ws_tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;user_ws_rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;unbounded_channel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;UnboundedReceiverStream&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;task&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="nf"&gt;.next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;user_ws_tx&lt;/span&gt;
                &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"websocket send error: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Save the sender in our list of connected users.&lt;/span&gt;
    &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Return a `Future` that is basically a state machine managing&lt;/span&gt;
    &lt;span class="c1"&gt;// this specific user's connection.&lt;/span&gt;

    &lt;span class="c1"&gt;// Every time the user sends a message, broadcast it to&lt;/span&gt;
    &lt;span class="c1"&gt;// all other users...&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_ws_rx&lt;/span&gt;&lt;span class="nf"&gt;.next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"websocket error(uid={}): {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nf"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// user_ws_rx stream will keep processing as long as the user stays&lt;/span&gt;
    &lt;span class="c1"&gt;// connected. Once they disconnect, then...&lt;/span&gt;
    &lt;span class="nf"&gt;user_disconnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Skip any non-Text messages...&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;User#{}&amp;gt;: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// New message from this user, send it to everyone else (except same uid)...&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_disconnected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_msg&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// The tx is disconnected, our `user_disconnected` code&lt;/span&gt;
                &lt;span class="c1"&gt;// should be happening in another task, nothing more to&lt;/span&gt;
                &lt;span class="c1"&gt;// do here.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;user_disconnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"good bye user: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Stream closed up, so remove from the user list&lt;/span&gt;
    &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;my_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Warp in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Functional approach.&lt;/li&gt;
&lt;li&gt;Very expressive.&lt;/li&gt;
&lt;li&gt;Strong ecosystem by being close to Tokio, Tower, and Hyper.&lt;/li&gt;
&lt;li&gt;Not the most beginner-friendly framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tide
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/http-rs/tide" rel="noopener noreferrer"&gt;Tide&lt;/a&gt; is a very minimalistic web framework that is built on top of the &lt;code&gt;async-std&lt;/code&gt; runtime. The minimalistic approach means that you get a very small API surface. Handler functions in Tide are &lt;code&gt;async fn&lt;/code&gt;s that take a &lt;code&gt;Request&lt;/code&gt; and return a &lt;code&gt;tide::Result&lt;/code&gt; of a &lt;code&gt;Response&lt;/code&gt;. Extracting data or sending the right response format is up to you.&lt;/p&gt;

&lt;p&gt;While this is arguably more work for you, it's also a lot more direct, meaning that you have full control over what's happening. For some cases, being able to be so close to HTTP request and response is a delight and makes things easier.&lt;/p&gt;

&lt;p&gt;Its middleware approach is similar to what you know from Tower, but Tide exposes the &lt;a href="https://crates.io/crates/async-trait" rel="noopener noreferrer"&gt;async trait crate&lt;/a&gt; to make implementation a lot easier. Since Tide is implement by folks who are also involved in the Rust async ecosystem, you can expect things like proper &lt;a href="https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html" rel="noopener noreferrer"&gt;async methods in traits&lt;/a&gt; which recently landed in Nightly, to be adopted quickly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tide Example
&lt;/h4&gt;

&lt;p&gt;User sessions example from their &lt;a href="https://github.com/http-rs/tide/blob/main/examples/sessions.rs" rel="noopener noreferrer"&gt;example repo&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[async_std::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;femme&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;log&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;LogMiddleware&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;SessionMiddleware&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;MemoryStore&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TIDE_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Please provide a TIDE_SECRET value of at &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;
                      least 32 bytes in order to run this example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="nf"&gt;.session_mut&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"visits"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or_default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"visits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="nf"&gt;.session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"visits"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"you have visited this website {} times"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/reset"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="nf"&gt;.session_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tide&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1:8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Tide in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Minimalistic approach.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;async-std&lt;/code&gt; runtime.&lt;/li&gt;
&lt;li&gt;Simple handler functions.&lt;/li&gt;
&lt;li&gt;Playground of async features.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Poem
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A program is like a poem, you cannot write a poem without writing it. --- Dijkstra&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/poem-web/poem" rel="noopener noreferrer"&gt;Poem&lt;/a&gt;'s Readme file greets you with these words. Poem claims to be a fully featured yet easy to use web framework. Bold claims, but Poem seems to deliver. At a first glance, it's usage is very similar to Axum, with the only difference that you need to mark handler functions with the respective macro. It also builds on Tokio and Hyper, and is fully compatible with Tower middleware, while still exposing its own middleware trait.&lt;/p&gt;

&lt;p&gt;Poem's middleware trait is also dead-simple to use. You can either implement the trait directly for all or specific &lt;code&gt;Endpoint&lt;/code&gt;s (Poem's way of expressing everything that can handle HTTP requests), or you just write an async function that accepts an &lt;code&gt;Endpoint&lt;/code&gt; as parameter. After dealing and sometimes struggling with &lt;a href="//outube.com/watch?v=z78_RnUPnpY"&gt;Tower and the Service Trait&lt;/a&gt; for such a long time, this is a breath of fresh air.&lt;/p&gt;

&lt;p&gt;Not only is Poem compatible with a lot of features from the broader ecosystem, it's also jam-packed with features itself, including full support for OpenAPI and Swagger docs. And it's not limited to HTTP based web services, it can also be used for gRPC services based on Tonic, or even in Lambda functions, without the need to switch frameworks. Add support for OpenTelemetry, Redis, Prometheus, and a lot more, and you check off all the boxes of a modern web framework for enterprise grade applications.&lt;/p&gt;

&lt;p&gt;Poem is still in a 0.x version, but if it keeps momentum and delivers a solid 1.0, this is a framework to look out for!&lt;/p&gt;

&lt;h4&gt;
  
  
  Poem Example
&lt;/h4&gt;

&lt;p&gt;An abbreviated version of the websocket chat from &lt;a href="https://github.com/poem-web/poem/blob/master/examples/poem/websocket-chat/src/main.rs" rel="noopener noreferrer"&gt;their example repo&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[handler]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="nf"&gt;.subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="nf"&gt;.on_upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="nf"&gt;.next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{name}: {text}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"/ws/:name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="nf"&gt;.data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1:3000"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Poem in a Nutshell
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Vast feature set.&lt;/li&gt;
&lt;li&gt;Compatible with the Tokio ecosystem.&lt;/li&gt;
&lt;li&gt;Easy to use.&lt;/li&gt;
&lt;li&gt;Adaptable for gRPC and Lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  On the Lookout
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pavex
&lt;/h3&gt;

&lt;p&gt;Initially I said that all Rust web frameworks look very similar at first glance. They are different in nuances, and sometimes do things better than others.&lt;/p&gt;

&lt;p&gt;Pavex is the exception to that rule. &lt;a href="https://github.com/LukeMathWalker/pavex" rel="noopener noreferrer"&gt;Pavex&lt;/a&gt; is currently being implemented by no other than Luca Palmieri, the author of the popular &lt;a href="https://www.zero2prod.com/index.html" rel="noopener noreferrer"&gt;Zero To Production&lt;/a&gt; book. You can say without a doubt that Luca knows what he is doing, and all his ideas and experience are going into Pavex.&lt;/p&gt;

&lt;p&gt;Pavex is significantly different as it sees itself as a &lt;em&gt;specialized compiler&lt;/em&gt; for building Rust APIs. It takes a high-level description of what your application should do, and the compiler a standalone API Server SDK crate, ready to be configured and launched.&lt;/p&gt;

&lt;p&gt;Pavex is still in its early stages, but it's definitely a project to keep an eye on. Check out Luca's &lt;a href="https://www.lpalmieri.com/posts/a-taste-of-pavex-rust-web-framework/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you can see, the world of Rust web frameworks is very diverse. There is no one-size-fits-all solution, and you need to pick the framework that fits your needs best. If you are just starting out, I recommend you to go with Actix or Axum, as they are the most beginner-friendly frameworks and they have gread documentation. Personally, I'm interested on what Pavex will bring to the table, and to be honest, after being a long time Axum user, I'm really interested in checking out Poem.&lt;/p&gt;

&lt;p&gt;And obviously, all of the major Rust web frameworks work with Shuttle, so &lt;a href="https://docs.shuttle.rs/examples/" rel="noopener noreferrer"&gt;try them out&lt;/a&gt; and see what works best for you!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>devops</category>
      <category>development</category>
    </item>
  </channel>
</rss>
