API Development in Rust: CORS, Tower Middleware, and the Power of Axum

amaendeepm

amaendeepm

Posted on October 21, 2024

API Development in Rust: CORS, Tower Middleware, and the Power of Axum

I started my Rust journey in August 2022, and it’s been an eye-opening experience. Rust is a language designed for performance and safety, but its web development ecosystem was still growing when I first encountered it. After experimenting with different frameworks and approaches, Axum stood out to me as a modern, intuitive solution.

With Axum, I’ve found building APIs not only productive but also enjoyable. Whether it’s implementing selective CORS controls, tracing requests with ease, or setting up API rate limits, the combination of Axum and Tower middleware just clicks.

CORS Control: Fine-Tuning Access

One of the first issues I had to tackle was implementing flexible CORS policies. I had an API with some public endpoints but also internal routes that needed restricted access. In many frameworks, managing this kind of fine-grained control can get messy. But Axum made this simple.

Using tower_http::cors, I could apply a global CORS policy while exempting specific routes, such as the /public route being open to all and /internal requiring strict access. Here’s a quick snippet of how I handled it:

use axum::{Router, routing::get};
use tower_http::cors::{CorsLayer, Any};
use tower::ServiceBuilder;

let cors_layer = CorsLayer::new()
    .allow_origin(Any)  // Open access to selected route
    .allow_methods(vec!["GET", "POST"]);

let app = Router::new()
    .route("/public", get(public_handler))
    .route("/internal", get(internal_handler))
    .layer(ServiceBuilder::new().layer(cors_layer));

Enter fullscreen mode Exit fullscreen mode

Axum allowed me to keep my security intact without complicating the setup, which I hadn’t found in other frameworks with this level of ease.

Tower Middleware: Tracing and Logging Done Right

I quickly became a fan of Tower, which integrates effortlessly with Axum. Setting up logging, request tracing, and metrics collection is as simple as adding layers to the router.

With tower_http::trace, I could capture detailed logs of requests and responses, all in a non-intrusive way:

use tower_http::trace::TraceLayer;
use tracing::Level;

let app = Router::new()
    .route("/api", get(api_handler))
    .layer(TraceLayer::new_for_http().with_level(Level::INFO));  
// Enable logging
Enter fullscreen mode Exit fullscreen mode

In a few lines of code, I had a fully instrumented API that logged every interaction. It made debugging and performance optimization much easier, and it felt like Axum was purpose-built for this kind of composability.

Rate Limiting and Protecting APIs

Rate limiting is another essential feature for any API-driven project. With tower’s middleware, I could easily cap the number of concurrent requests using the ConcurrencyLimitLayer. Again, the integration with Axum was smooth:

use tower::limit::ConcurrencyLimitLayer;

let app = Router::new()
    .route("/api/limited", get(limited_handler))
    .layer(ConcurrencyLimitLayer::new(100));  // Limit to 100

Enter fullscreen mode Exit fullscreen mode

This setup ensures that my server remains responsive under load, which is crucial when handling high-traffic APIs.

Why Axum Outshines Raw Tokio and Actix

During my journey, I spent some time exploring both raw Tokio and Actix as alternatives. While both are powerful in their own right, I found Axum to be a step ahead in terms of developer experience and intuitiveness.

  1. Axum vs Raw Tokio: Raw Tokio is fantastic when you need granular control over asynchronous operations, and its event-driven model is second to none. But when it comes to building web APIs, managing everything at the Tokio level can quickly become complex and verbose. Axum, built on top of Tokio, abstracts much of the boilerplate while maintaining full async capabilities. It’s like having the power of Tokio without the steep learning curve. You can focus on building your application rather than handling the low-level details. With Axum, you don’t need to manage HTTP requests manually or write tons of code to set up routing, middleware, or async logic. Axum does all of this in a much cleaner, modular way, making it the next-generation framework over raw Tokio for web applications.

  2. Axum vs Actix: Actix Web was one of the first Rust web frameworks to gain serious traction, and it’s known for its high performance. However, I found Actix’s actor-based model to be more complex than necessary for most web use cases. While Actix shines in certain scenarios, the actor pattern can be an overkill for basic API services. Axum, in contrast, uses a more familiar and intuitive routing model. Its design follows the simplicity of frameworks like Express in JavaScript or Flask in Python, but with all the benefits of Rust’s type safety and performance. And yet, it doesn’t compromise on advanced features like middleware stacking, error handling, or async support. Plus, Axum’s reliance on Tower middleware gives you access to a wide range of ready-made tools (like CORS handling, rate limiting, and tracing) that are easy to plug into your API.

  3. Tokio and Tower at its Core: Axum is also deeply integrated with Tokio and Tower, which are two of the most battle-tested and high-performance libraries in the Rust ecosystem. This means that Axum is not only fast but also scalable and production-ready. Whether it’s handling high concurrency with Tokio’s async runtime or layering on functionality with Tower middleware, Axum is designed to scale without getting in your way.

Why I’m Loving Axum

Axum has made my Rust web development journey far smoother than I anticipated. The fact that it allows me to easily control CORS per route, integrate logging and tracing, and enforce API rate limits—without over-complicating things—proves it’s a framework designed with developers in mind.

Here’s what I love most:

Elegant design: Routing, middleware, and error handling all feel natural. You don’t need to fight the framework to build complex APIs.
Composable and modular: Tower middleware makes it easy to layer in functionality like logging, rate limiting, and CORS control without cluttering your application logic.
Power of async: Axum’s seamless integration with Tokio gives me full control over asynchronous operations without adding unnecessary complexity.

Anyone looking to build web APIs in Rust, Axum can be easily your first choice. It offers the right balance of simplicity, power, and flexibility, making it a worthy successor to both raw Tokio setups and older frameworks like Actix. Whether building a small personal project or scaling up an enterprise service, Axum has the tools and developer experience worth exploring.

💖 💪 🙅 🚩
amaendeepm
amaendeepm

Posted on October 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related