DEV Community

Eastspire
Eastspire

Posted on

A Rust Newbie’s Journey in Developing a Campus API

As a junior computer science student, I was working on a campus second-hand trading platform project last semester when I stumbled upon the Hyperlane Rust HTTP framework. I was in a dilemma about choosing a framework— it needed to be powerful enough to handle the peak trading at the end of the semester, and its syntax had to be simple so that I, as a Rust newbie, could get up to speed quickly. To my pleasant surprise, Hyperlane exceeded all my expectations. Today, I want to share my experience with this amazing framework!

I. First Encounter with ctx: Such Thoughtful Abstraction!

When I first started writing route functions, I was amazed by Hyperlane’s Context (or ctx for short). I remember the first time I wanted to get the request method. In traditional Rust HTTP frameworks, I would have to write:

let method = ctx.get_request().await.get_method();
Enter fullscreen mode Exit fullscreen mode

But Hyperlane flattened the methods. Now, I simply write:

let method = ctx.get_request_method().await;
Enter fullscreen mode Exit fullscreen mode

It’s like organizing a backpack with layers. The framework has renamed the subfields of requests and responses according to certain rules. Setting the response status code changed from set_status_code to set_response_status_code. Although it’s a few more letters, the code logic is as clear as a flowchart. I no longer need to flip through the documentation to find the method hierarchy!

II. Route Macros: A Boon for the Lazy

What really hooked me was its request method macros. When I wrote the homepage route, I tried using the #[methods(get, post)] combined annotation, which was much simpler than declaring each enum value one by one. Later, I found out I could even simplify it to #[get]. Suddenly, writing routes felt as easy as writing Markdown:

#[get]
async fn ws_route(ctx: Context) {
    let key = ctx.get_request_header(SEC_WEBSOCKET_KEY).await.unwrap();
    let body = ctx.get_request_body().await;
    ctx.set_response_body(key).await.send_body().await;
    ctx.set_response_body(body).await.send_body().await;
}
Enter fullscreen mode Exit fullscreen mode

Once, my teammate mistakenly wrote #[postman] instead of #[post], but the framework threw a friendly error message. Unlike some frameworks that just throw a compilation error, Hyperlane is super friendly to beginners!

III. Middleware Onion Model: Peeling Back the Layers of Request Processing

When I was working on user authentication, I first understood the beauty of the middleware onion model. I drew a flowchart according to the documentation (even though my Mermaid diagram was a bit crooked) and realized that a request travels from the outer layer of the onion inward:

graph TD
    A[Client Request] --> B[Authentication Middleware]
    B --> C[Logging Middleware]
    C --> D[Controller]
    D --> E[Response Formatting Middleware]
    E --> F[Client Response]
Enter fullscreen mode Exit fullscreen mode

I wrote a JWT verification middleware. When it detects an invalid token, I can simply use ctx.aborted() to terminate the subsequent process. This "short-circuit" operation is much better than repeating the verification logic in each route. I remember once, to debug the middleware order, I deliberately placed the logging middleware after authentication. As a result, the request logs were full of authentication errors, and I realized how strict the middleware order is, just like the layers of an onion!

IV. WebSocket Support: Instant Real-Time Chat Functionality

The most headache-inducing part of the project was the real-time chat feature. To my surprise, Hyperlane’s WebSocket lifecycle is designed very clearly. According to the documentation’s flowchart:

graph TD
    A[Client Connection] --> Z[Pre-upgrade Processing]
    Z --> Y[WebSocket Handshake]
    Y --> X[Connection Established Callback]
    X --> B[Middleware Processing]
    B --> C[Message Handling Controller]
    C --> D[Response Handling]
Enter fullscreen mode Exit fullscreen mode

I spent only one evening completing the WebSocket module. The ctx.closed() method, in particular, can actively close the connection when a user exits the chat. During testing, I found that even with 100 people chatting online simultaneously, the server resource usage remained stable. My roommate once wrote the same feature in Node.js, but it crashed during a 50-person test. The comparison made me feel a great sense of achievement!

V. Dynamic Routing: Adding Regex to Parameters is Fun

When writing the product detail page route, I used dynamic parameters. The ordinary route /goods/{id} is easy to understand, but when I needed to restrict the parameter to numbers, I found I could write:

server.route("/goods/{id:\\d+}", |ctx| async move {
    let id = ctx.get_route_param("id").await.parse::<u32>().unwrap();
    // Database query logic...
}).await;
Enter fullscreen mode Exit fullscreen mode

This regex matching parameter method reminded me of the Regex homework I did in class. However, the framework has encapsulated the complex parsing process. Once, I mistakenly wrote the regex as {id:\\D+}, and the framework returned a 404 instead of a server error. Later, I found out that this is its route error handling mechanism, and the attention to detail is truly impressive!

VI. Performance Testing: Faster than Gin?!

Before the final course presentation, I conducted a performance test using wrk with the command:

wrk -c360 -d60s http://127.0.0.1:6000/
Enter fullscreen mode Exit fullscreen mode

The result left me speechless: Hyperlane’s QPS reached over 320,000, nearly 30% faster than the same interface written by my roommate using Gin! Although it’s a bit slower than the underlying Tokio library, as an upper-layer framework, this performance is more than enough to support thousands of students using it simultaneously. During the defense, when the teacher saw this data, he specifically asked me if I had secretly optimized the server. In fact, I just ran it with the default configuration from the documentation!

VII. From Pitfalls to Love: The Growth Path of a Rust Framework

At the beginning of using Hyperlane, I also encountered many pitfalls. For example, in versions before v4.0.0, the execution order of synchronous routes and asynchronous middleware made me debug for an entire afternoon. Another time, I forgot to call send_body() in the WebSocket processing, which prevented messages from being sent. But every time I checked the documentation, I could find a clear version description. Especially the lifecycle evolution chart, the changes from v3.0.0 to v5.25.1 are very clear:

  • After v4.22.0, ctx.aborted() can interrupt requests, much like a "pause skill" in a game.
  • ctx.closed() in v5.25.1 can actively close connections, solving the long-connection resource leakage problem I encountered before.

Now the project has been deployed on the school server, handling hundreds of transactions every day, and Hyperlane has never let me down. As a newbie transitioning from C++ to Rust, I sincerely feel that this framework balances performance and ease of use. It is especially friendly to student developers—the example code in the documentation can be copied and used directly, unlike some frameworks that require a lot of time to study the architecture before getting started.

If you are also working on a Rust Web project, I strongly recommend giving Hyperlane a try! The feeling of writing code like building with building blocks really makes programming enjoyable!

Note on the URL

I noticed that you mentioned a URL (http://127.0.0.1:6000/). It seems there was an issue with resolving the webpage. This could be due to network issues or an invalid link. Please check the URL’s validity and try accessing it again. If you need further assistance with the content of that webpage, feel free to let me know!

Top comments (0)