Skip to content

Instantly share code, notes, and snippets.

@iSeiryu
Last active December 14, 2024 22:38
Show Gist options
  • Save iSeiryu/c1b95242af000a11ea4710b79c2d6a53 to your computer and use it in GitHub Desktop.
Save iSeiryu/c1b95242af000a11ea4710b79c2d6a53 to your computer and use it in GitHub Desktop.
Simple get and post endpoints with C# vs NodeJS vs Rust

Program.cs

using System.Text.Json.Serialization;

var app = WebApplication.CreateBuilder(args).Build();

app.MapGet("/hi", () => "hi");
app.MapPost("send-money", (SendMoneyRequest request) =>
{
    var receipt = new Receipt($"{request.From.FirstName} {request.From.LastName}",
                              $"{request.To.FirstName} {request.To.LastName}",
                              request.Amount,
                              DateTime.Now,
                              $"{request.To.Address.Street}, {request.To.Address.City}, {request.To.Address.State}, {request.To.Address.Zip}");
    return receipt;
});

app.Run();



record AccountHolder(Guid Id, string FirstName, string LastName, Address Address, string Email);
record Address(string Street, string City, [property: JsonConverter(typeof(JsonStringEnumConverter))] State State, string Zip);
record SendMoneyRequest(AccountHolder From, AccountHolder To, decimal Amount, DateTime SendOn);
record Receipt(string FromAccount, string ToAccount, decimal Amount, DateTime CreatedOn, string ToAddress);
enum State { CA, NY, WA, TX, FL, IL, PA, OH, GA, MI, NC, NJ, VA }

myapp.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

</Project>

Benchmark via hey

hey -n 200000 -c 100 -m POST -H 'Content-Type: application/json' -d '{
  "from": {
      "id": "b1b9b3b1-3b1b-3b1b-3b1b-3b1b3b1b3b1b",
      "firstName": "John",
      "lastName": "Doe",
      "address": {
          "street": "123 Main St",
          "city": "Anytown",
          "state": "CA",
          "zip": "12345"
      },
      "email": "[email protected]"
  },
  "to": {
      "id": "7eb53909-8977-4a7d-8e91-f1bfcfe812e2",
      "firstName": "Jane",
      "lastName": "Doe",
      "address": {
          "street": "456 Elm St",
          "city": "Anytown",
          "state": "FL",
          "zip": "12345"
      },
      "email": "[email protected]"
  },
  "amount": 30.14,
  "sendOn": "2024-06-01T12:00:00"
}' http://localhost:5000/send-money
@iSeiryu
Copy link
Author

iSeiryu commented May 24, 2024

Rust with https://github.com/tokio-rs/axum

use axum::{
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use chrono;
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/hi", get(hi))
        // `POST /users` goes to `create_user`
        .route("/send-money", post(send_money));

    // run our app with hyper
    let listener = tokio::net::TcpListener::bind("127.0.0.1:8000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

// basic handler that responds with a static string
async fn hi() -> &'static str {
    "hi"
}

async fn send_money(Json(request): Json<SendMoneyRequest>) -> impl IntoResponse {
    let receipt = Receipt {
        from_account: format!("{} {}", request.from.firstName, request.from.lastName),
        to_account: format!("{} {}", request.to.firstName, request.to.lastName),
        amount: request.amount,
        created_on: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
        to_address: format!(
            "{}, {}, {}, {}",
            request.to.address.street,
            request.to.address.city,
            request.to.address.state,
            request.to.address.zip
        ),
    };

    Json(receipt)
}

#[derive(Deserialize)]
struct AccountHolder {
    id: Uuid,
    firstName: String,
    lastName: String,
    address: Address,
    email: String,
}

#[derive(Deserialize)]
struct Address {
    street: String,
    city: String,
    state: State,
    zip: String,
}

#[derive(Deserialize)]
struct SendMoneyRequest {
    from: AccountHolder,
    to: AccountHolder,
    amount: f64,
    sendOn: String, // DateTime is not directly serializable in serde, use String instead
}

#[derive(Deserialize, Serialize)]
struct Receipt {
    from_account: String,
    to_account: String,
    amount: f64,
    created_on: String, // DateTime is not directly serializable in serde, use String instead
    to_address: String,
}

#[derive(Serialize, Deserialize)]
enum State {
    CA,
    NY,
    WA,
    TX,
    FL,
    IL,
    PA,
    OH,
    GA,
    MI,
    NC,
    NJ,
    VA,
}

impl fmt::Display for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            State::CA => write!(f, "CA"),
            State::NY => write!(f, "NY"),
            State::WA => write!(f, "WA"),
            State::TX => write!(f, "TX"),
            State::FL => write!(f, "FL"),
            State::IL => write!(f, "IL"),
            State::PA => write!(f, "PA"),
            State::OH => write!(f, "OH"),
            State::GA => write!(f, "GA"),
            State::MI => write!(f, "MI"),
            State::NC => write!(f, "NC"),
            State::NJ => write!(f, "NJ"),
            State::VA => write!(f, "VA"),
        }
    }
}

Cargo.toml

[package]
name = "axum-app"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
axum = { version = "0.7.5" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = "0.4"
uuid = { version = "0.8", features = ["v4", "serde"] }

@iSeiryu
Copy link
Author

iSeiryu commented Jul 10, 2024

wrk is a lot faster than hey.
https://github.com/wg/wrk/

./wrk -t1 -c1 -d1s http://localhost:5000/send-money -s scripts/post.lua

scripts/post.lua

-- example HTTP POST script which demonstrates setting the
-- HTTP method, body, and adding a header

wrk.method = "POST"
wrk.body   = [[
    {
    "from": {
        "id": "b1b9b3b1-3b1b-3b1b-3b1b-3b1b3b1b3b1b",
        "firstName": "John",
        "lastName": "Doe",
        "address": {
            "street": "123 Main St",
            "city": "Anytown",
            "state": "CA",
            "zip": "12345"
        },
        "email": "[email protected]"
    },
    "to": {
        "id": "7eb53909-8977-4a7d-8e91-f1bfcfe812e2",
        "firstName": "Jane",
        "lastName": "Doe",
        "address": {
            "street": "456 Elm St",
            "city": "Anytown",
            "state": "FL",
            "zip": "12345"
        },
        "email": "[email protected]"
    },
    "amount": 30.14,
    "sendOn": "2024-06-01T12:00:00"
  }
  ]]
wrk.headers["Content-Type"] = "application/json"

function response(status, headers, body)
    --print(status)
    --print(body)
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment