Skip to content

Instantly share code, notes, and snippets.

@iSeiryu
Last active July 10, 2024 17:53
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": "john.doe@somewhere.com"
  },
  "to": {
      "id": "7eb53909-8977-4a7d-8e91-f1bfcfe812e2",
      "firstName": "Jane",
      "lastName": "Doe",
      "address": {
          "street": "456 Elm St",
          "city": "Anytown",
          "state": "FL",
          "zip": "12345"
      },
      "email": "jane.doe@somewhereelse.com"
  },
  "amount": 30.14,
  "sendOn": "2024-06-01T12:00:00"
}' http://localhost:5000/send-money
@iSeiryu
Copy link
Author

iSeiryu commented May 21, 2024

NodeJS with ExpressJS

const express = require('express');
const app = express();
app.use(express.json());

class AccountHolder {
    constructor(id, firstName, lastName, address, email) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.address = address;
        this.email = email;
    }
}

class Address {
    constructor(street, city, state, zip) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zip = zip;
    }
}

class SendMoneyRequest {
    constructor(from, to, amount, sendOn) {
        this.from = from;
        this.to = to;
        this.amount = amount;
        this.sendOn = sendOn;
    }
}

class Receipt {
    constructor(fromAccount, toAccount, amount, createdOn, toAddress) {
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.amount = amount;
        this.createdOn = createdOn;
        this.toAddress = toAddress;
    }
}

const State = { CA: 'CA', NY: 'NY', WA: 'WA', TX: 'TX', FL: 'FL', IL: 'IL', PA: 'PA', OH: 'OH', GA: 'GA', MI: 'MI', NC: 'NC', NJ: 'NJ', VA: 'VA' };

app.get('/hi', (req, res) => {
    res.send('hi');
});

app.post('/send-money', (req, res) => {
    const request = req.body;
    const receipt = new Receipt(
        `${request.from.firstName} ${request.from.lastName}`,
        `${request.to.firstName} ${request.to.lastName}`,
        request.amount,
        new Date(),
        `${request.to.address.street}, ${request.to.address.city}, ${request.to.address.state}, ${request.to.address.zip}`
    );
    res.json(receipt);
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

@iSeiryu
Copy link
Author

iSeiryu commented May 21, 2024

Rust with Rocket

#[macro_use]
extern crate rocket;

use chrono;
use rocket::serde::{json::Json, Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;

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

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct Address {
    street: String,
    city: String,
    state: State,
    zip: String,
}

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

#[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
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)]
#[serde(crate = "rocket::serde")]
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"),
        }
    }
}

#[get("/hi")]
fn index() -> &'static str {
    "hi"
}

#[post("/send-money", data = "<request>")]
fn send_money(request: Json<SendMoneyRequest>) -> Json<Receipt> {
    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)
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index, send_money])
}

Cargo.toml

[package]
name = "rustapp"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
uuid = { version = "0.8", features = ["v4", "serde"] }
chrono = "0.4"

@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": "john.doe@somewhere.com"
    },
    "to": {
        "id": "7eb53909-8977-4a7d-8e91-f1bfcfe812e2",
        "firstName": "Jane",
        "lastName": "Doe",
        "address": {
            "street": "456 Elm St",
            "city": "Anytown",
            "state": "FL",
            "zip": "12345"
        },
        "email": "jane.doe@somewhereelse.com"
    },
    "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