Monthly Archives: August 2025

A simple web API in various languages and deployable to Kubernetes (Elixir)

Continuing this short series of writing a simple echo service web API along with the docker and k8s requirements, we’re now going to turn our attention to a Elixir implementation.

Implementation

I’m going to be using Visual Code and dev containers, so I created a folder echo_service which has a folder named .devcontainer with the following devcontainer.json

{
    "image": "elixir",
    "forwardPorts": [3000]
}

Next I opened Visual Code in the echo_service folder and it should detect the devtonainer and ask if you want to reopen in the devcontainer. To which we do.

I’m going to use Phoenix Server (phx), so I open the terminal in Visual Code and run the following

  • I needed to install phx installer, using

    mix archive.install hex phx_new
    
  • Next I want to generate a minimal phx server, hence run the following

    mix phx.new echo_service --no-html --no-ecto --no-mailer --no-dashboard --no-assets --no-gettext
    

    When this prompt appears, type y

    Fetch and install dependencies? [Yn]
    
  • Now cd into the newly created echo_service folder
  • To check everything is working, run
    mix phx.server
    

Next we need to add a couple of controllers (well we could just use one but I’m going to create a Echo controller and a Health controller). So in lib/echo_service_web/controllers add the files echo_controller.ex and health_controller.ex

The echo_controller.ex looks like this

defmodule EchoServiceWeb.EchoController do
  use Phoenix.Controller, formats: [:html, :json]

  def index(conn, %{"text" => text}) do
    send_resp(conn, 200, "Elixir Echo: #{text}")
  end
end

The health_controller.exe should look like this

defmodule EchoServiceWeb.HealthController do
  use Phoenix.Controller, formats: [:html, :json]

  def livez(conn, _params) do
    send_resp(conn, 200, "Live")
  end

  def readyz(conn, _params) do
    send_resp(conn, 200, "Ready")
  end
end

In the parent folder (i.e. lib/echo_service_web) edit the router.exe so it looks like this

defmodule EchoServiceWeb.Router do
  use EchoServiceWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", EchoServiceWeb do
    # pipe_through :api
    get "/echo", EchoController, :index
    get "/livez", HealthController, :livez
    get "/readyz", HealthController, :readyz
  end
end

Now we can run mix phx.server again (ctrl+c twice to shut any existing running instance).

Dockerfile

Next up we need to create our Dockerfile

FROM elixir:latest

RUN mkdir /app
COPY . /app
WORKDIR /app

RUN mix local.hex --force
RUN mix do compile

ENV PORT=8080
EXPOSE 8080

CMD ["mix", "phx.server"]

Note: In Linux port 80 might be locked down, hence we use port 8080 – to override the default port in phx we also set the environment variable PORT.

To build this, run

docker build -t putridparrot.echo_service:v1 .

Don’t forget to change the name to your preferred name.

To test this, run

docker run -p 8080:8080 putridparrot.echo_service:v1

Kubernetes

If all wen well we’ve not tested our application and see it working from a docker image, so now we need to create the deployment etc. for Kubernete’s. Let’s assume you’ve pushed you image to Docker or another container registry such as Azure – I’m call my container registry putridparrotreg.

I’m also not going to use helm at this point as I just want a (relatively) simple yaml file to run from kubectl, so create a deployment.yaml file, we’ll store all the configurations, deployment, service and ingress in this one file jus for simplicity.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  namespace: dev
  labels:
    app: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
      - name: echo
        image: putridparrotreg/putridparrot.echo_service:v1
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "100Mi"
            cpu: "100m"
          limits:
            memory: "200Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: echo_service
  namespace: dev
  labels:
    app: echo
spec:
  type: ClusterIP
  selector:
    app: echo 
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
  namespace: dev
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: mydomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: echo_service
            port:
              number: 80

Don’t forget to change the “host” and image to suit, also this assume you created a namespace “dev” for your app. See Creating a local container registry for information on setting up your own container registry.

Running ollama locally

Installing ollama locally is easily done using docker, for example

docker run -d -v "c:\temp\ollama:/root/.ollama" -p 11434:11434 --name ollama ollama/ollama

Next we’ll want to pull in a model, for example using phi3

docker exec -it ollama ollama run phi3 

We have several phi3 models, phi3:mini, phi3:medium and phi3:medium-128k (requires Ollama 0.1.39+).

Other options include mistral, llama2 or openhermes. Just replace phi3 with your preferred model

On running the exec command we get a “prompt” and can start a “chat” with the model.

Use “/bye” to exit the prompt.

Using Valkey (a Redis compatible memory data store)

Valkey is an in-memory, high performance key/value store. It’s Redis compatible which means we can use the same protocols, clients etc.

We can run up an instance via docker using

docker run --name valkey-cache -d -p 6379:6379 valkey/valkey

The valkey client can be run from the instance using

docker exec -ti valkey-cache valkey-cli

As mentioned, we can use existing tools that work with Redis, so here’s a docker-compose.yaml to run up a Valkey instance along with redis-commander

services:
  valkey:
    image: valkey/valkey
    container_name: valkey
    ports:
      - "6379:6379"

  redis-commander:
    image: rediscommander/redis-commander:latest
    container_name: redis-commander
    ports:
      - "8080:8080"
    environment:
      REDIS_HOSTS: valkey:valkey:6379

Now we can use localhost:8080 and view/interact with our data store via a browser.

valkey-cli

From the valkey-cli we can run various commands to add a key/value, get it, delete it etc.

CommandDescription
KEYS '*'List all keys
SET mykey "Hello"add/set a key/value
GET mykeyget the value for the given key
EXISTS mykeyCheck if the key exists
DEL mykeyDelete the key/value
EXPIRE mykey 60Set an expire on the key (60 seconds in this example)
TTL mykeyChecks the time to live for a key

WASM with Rust (and Yew)

In my previous post WASM with Rust (and Leptos) we covered creating a Rust project which generate a binary for use within WASM, using Leptos and using Trunk to build and run it.

There’s more than one framework for creating WASM/WebAssembly projects in Rust, let’s look at another one, this time Yew.

We’ll be using trunk (just as the previous post) to serve but I’ll repeat the step to install here

cargo install trunk

I’m going to assume you’ve also added the target, but I’ll include here for completeness

rustup target add wasm32-unknown-unknown

Getting started

We’re going to use a template to scaffold a basic Yew application, so create yourself a folder for your project then run

cargo generate --git https://github.com/yewstack/yew-trunk-minimal-template

For mine I stuck with the defaults after naming it wasm_app. So the stable Yew version and no logging.

Before we get into the code, let’s add a Trunk.toml (in the folder with the Cargo.toml) with this configuration

[serve]
address = "127.0.0.1"
port = 8081

Let’s see what Yew generated. From the app folder (mine was named wasm_app) run

trunk serve --open

Straight up, Yew gives us a colourful starting point.

In the code

Let’s go through the code, so we know what we need if we’re creating a project without the template, but also to see what’s been added.

If you check out the Cargo.toml it’s filled in a lot of package info. for us, so you might wish to go tweak there, but we have a single dependency

[dependencies]
yew = { version="0.21", features=["csr"] }

The Yew template includes index.scss for our styles and Trunk automatically compiles/transpiles to the .css file of the same name within the dist.

The index.html is lovely and simple, really the only addition from a bare bones index.html is the including the SASS link which tells the compiler to compile using SASS

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Trunk Template</title>
    <link data-trunk rel="sass" href="index.scss" />
  </head>
  <body></body>
</html>

In the src folder we have two files, main.rs and app.rs, within main.rs we have

mod app;

use app::App;

fn main() {
    yew::Renderer::<App>::new().render();
}

Here we are basically telling Yew to render our App. Within the app.rs we have

use yew::prelude::*;

#[function_component(App)]
pub fn app() -> Html {
    html! {
        <main>
            <img class="logo" src="https://yew.rs/img/logo.svg" alt="Yew logo" />
            <h1>{ "Hello World!" }</h1>
            <span class="subtitle">{ "from Yew with " }<i class="heart" /></span>
        </main>
    }
}

Similar to Leptos, we have a macro for our HTML tags etc. but it’s html! here (not view!). Also the component is marked with the function_component annotation, but otherwise it’s very recognisable what’s happening here.

use yew::prelude::*;

#[function_component(App)]
pub fn app() -> Html {
    html! {
        <main>
            <img class="logo" src="https://yew.rs/img/logo.svg" alt="Yew logo" />
            <h1>{ "Hello World!" }</h1>
            <span class="subtitle">{ "from Yew with " }<i class="heart" /></span>
        </main>
    }
}

Let’s add some routing

Create yourself a new file named counter.rs, let’s implement the fairly standard counter.rs component – I should say the Yew web site has an example of the counter page on their Getting Started, so we’ll just take that and make a few tweaks

use yew::prelude::*;

#[function_component(Counter)]
pub fn counter() -> Html {
    let counter = use_state(|| 0);
    let on_add_click = {
        let c = counter.clone();
        move |_| { c.set(*c + 1); }
    };

    let on_subtract_click = {
        let c = counter.clone();
        move |_| { c.set(*c - 1); }
    };

    html! {
        <div>
            <button onclick={on_add_click}>{ "+1" }</button>
            <p>{ *counter }</p>
            <button onclick={on_subtract_click}>{ "-1" }</button>
        </div>
    }
}

If you’ve used React, you’ll see this is very similar to the way we might write our React component.

Ofcourse the syntax differs, but we have a use_state and event handler functions etc. The main difference is the way we’re cloning the value – by convention all those c variables would be named counter as well, but I wanted to make it clear as to what the scope of the counter variable was.

On further reading – it appears the use_XXX syntax are hooks, see Pre-defined Hooks

When we clone the counter, we’re not cloning the value, we’re cloning the handle (or type UseStateHandler which implements Clone). All clones point to the same reactive cell, so you are essentially changing the value in that handle.

Before trying this code out we need our router, so the Yew site says add the following dependency to the Cargo.toml file

yew-router = { git = "https://github.com/yewstack/yew.git" }

but I had version issues so instead used

yew-router = { version = "0.18.0" }

Now let’s change the app.rs file to the following

use yew::prelude::*;
use yew_router::prelude::*;
use crate::counter::Counter;

#[derive(Clone, Routable, PartialEq)]
enum Route {
    #[at("/")]
    Home,
    #[at("/counter")]
    Counter,
    #[not_found]
    #[at("/404")]
    NotFound,
}

fn switch(routes: Route) -> Html {
    match routes {
        Route::Home => html! { <h1>{ "Home" }</h1> },
        Route::Counter => { html! { <Counter /> }},
        Route::NotFound => html! { <h1>{ "404" }</h1> },
    }
}

#[function_component(App)]
pub fn app() -> Html {
    html! {
        <BrowserRouter>
            <Switch<Route> render={switch} />
        </BrowserRouter>
    }
}

There’s a fair bit to digest, but hopefully it’s fairly obvious what’s happening thankfully.

We create an enum of the routes with the at annotation mapping to the URL path. Then we use a function (named switch in this case) which maps the enum to the HTML. We’ve embedded HTML into the Home and NotFound routes but the Counter will render our Counter component as if it’s HTML.

The final change is the app functions where we use the BrowserRouter and Switch along with our switch function to render the pages.

Code

Checkout the code on GitHub

WASM with Rust (and Leptos)

I’m going to be going through some of the steps from Leptos Getting Started. Hopefully we’ll be able to add something here.

Prerequisites

We’re going to use Trunk to run our application, so first off we need to make sure we’ve installed it

cargo install trunk

Trunk allows us to build our code, run a server up and run our WASM application, moreover it’s watching for changes and so will rebuild and redeploy things as you go. Web style development with a compiled language. We’ll cover more on trunk later.

Creating our project

Create yourself a folder for your application and run the terminal in that folder.

We need to create our project – although I use RustRover from JetBrains, let’s go “old school” as use cargo to create our project etc.

Run the following (obviously change wasm_app to something more meaningful for your app name

cargo new wasm_app --bin

cd into the application (as above, mine’s named wasm_app).

cargo add leptos --features=csr

We’ll need to add the WASN target

rustup target add wasm32-unknown-unknown

in the root folder (where your Cargo.toml file is) create an index.html with the following

<!DOCTYPE html>
<html>
  <head></head>
  <body></body>
</html>

Next create a cd into the src folder and edit the main.rs – it should look like the following

use leptos::prelude::*;

fn main() {
    leptos::mount::mount_to_body(|| view! { <p>"Hello, world!"</p> })
}

The mount_to_body, as the name suggests, essentially injects your WASM code into the <body></body> element.

Now, from the root folder (i.e. where index.html is) run

trunk serve --open

If the default port (8080) is already in use we can specify the port using

trunk serve --open --port 8081

If all went well then you see your default browser showing the web page, if not then open a browser window and navigate to http://localhost:8081/

To save having to set the port via the CLI, you can also create a trunk.toml file in the root folder with something like this in it

[serve]
address = "127.0.0.1"
port = 8081

Taking it a bit further

We’ve got ourselves a really simple WASM page.

Let’s move this a little further by creating a component for our application.

Create a file in src named app.rs and we’ll add the following

use leptos::prelude::*;

#[component]
pub fn App() -> impl IntoView {
    view! {
        <p>"Hello, world!"</p>
    }
}

and change the main.rs to this

mod app;

use leptos::mount::mount_to_body;
use crate::app::App;

fn main() {
    mount_to_body(App);
}

If you kept trunk running it will automatically rebuild the code and refresh the browser.

Components

The component (below) returns HTML via the view macro and as you can see, this returns a trait IntoView. As you can see we mark the function as a #[component]

#[component]
pub fn App() -> impl IntoView {
    view! {
        <p>"Hello, world!"</p>
    }
}

Note: you might want to change your text to prove to yourself that the did indeed get updated in the web page when you saved it – assuming trunk was running.

This is a pretty simple starting point, so let’s add some more bits to this…

Let’s change the App function to this

use leptos::prelude::*;
 
#[component]
pub fn App() -> impl IntoView {
    let (count, set_count) = signal(0);

    view! {

        <button on:click=move |_| set_count.set(count.get() + 1)>Up</button>
        <div>{count}</div>
        <button on:click=move |_| set_count.set(count.get() - 1)>Down</button>
    }
}

The signal (which is a reactive variable) may remind you of something like useState in React, we deconstruct the signal (which has the default of 0) into a count (getter) and a set_count (setter). To be honest the set and get functions seem odd if you’re using the properties such as C#, but that’s the way it is is Rust.

The count value is of type ReadSignal and set_count is of type WriteSignal.

We can also set the value of count using the following within the closure. Ultimately this should be a more performant way of doing things. The example above might be preferred for readability (although that’s debatable) – it does however look more inline with the way we get values etc. I’ll leave others to debate the pros and cons, for me the line below is effecient.

*set_count.write() += 1

Routing

Let’s rename the app.rs file to counter.rs (also rename the function to Counter) and create a new app.rs file which will acts as the router to our components. We’ll need to add this to the Cargo.toml dependencies

leptos_router = "0.8.5"

and in the app.rs paste the following code

use leptos::prelude::*;
use leptos_router::{
    components::{Route, Router, Routes},
    StaticSegment,
};
use crate::counter::Counter;
use crate::home::Home;

#[component]
pub fn App() -> impl IntoView {
    view! {
        <Router>
            <Routes fallback=|| "Page not found.">
                <Route path=StaticSegment("") view=Home />
                <Route path=StaticSegment("counter") view=Counter />
            </Routes>
        </Router>
    }
}

You’ll need to add the mod to the main.rs file to include the home reference.

I’ve also added a home.rs file with the following

use leptos::prelude::*;

#[component]
pub fn Home() -> impl IntoView {
    view! {
        <p>Welcome to your new app!</p>
    }
}

As you can see, the router routes / to our Home component and the /counter to our Counter component.

Meta data from code

Whilst we have an index.html which you can edit, we might want to supply some of the meta data etc. via the app’s code.

Add the following to Cargo.toml dependencies

leptos_meta = "0.8.5"

Now in main.rs add

use leptos::view;
use leptos_meta::*;

and now change the code to

fn main() {
    mount_to_body(|| {
        provide_meta_context();
        view! { 
            <Title text="Welcome to My App" />
            <Meta name="description" content="This is my app." />
            <App />
        }
    });
}

The provide_meta_context() function allows us to inject metadata such as <title>, <meta> and <script>

Code

Code for this post is available on GitHub.

cargo-watch

Cargo watch allows us to run our Rust application and when changes are detected updates and runs the application again (you know, standard watch functionality).

To install use

cargo install cargo-watch

The to run, use

cargo-watch -x run

Async/await using tokio and Rust

Rust supports async/await in a similar way to C# although these are supplied via runtimes, for example Tokio, async-std and others.

In this post we’ll look at the tokio runtime option.

The first thing we need to do is add tokio to the Cargo.toml, for example

[dependencies]
tokio = { version = "1", features = ["full"] }

Now, let’s create a simple async function

async fn execute() {
   println!("Execution in async function");
}

Notice we do not return a Task like C# or any type in this case, but this is essentially syntactic sugar for

fn execute() -> impl Future<Output = ()> 

Hence, we can see async functions return a Future (similar to a Promise in Javascript etc.).

The Future trait has a poll function which can be checked to see if the async function is ready to return a value or if it’s pending.

To await an async function we use te following syntax

execute().await;

The await will ofcourse cause the current Future to return to the caller but the code after the await will not execute until the Future completes/is ready.

If you come from C# this is much the same, i.e. running a continuation when completed etc.

To use asyc/await on main we need to make a couple of changes, first to make main async but this alone will not work without the runtime, hence main looks like this

#[tokio::main]
async fn main() {
  execute().await;
}

Futures are lazy loaded. Meaning, that the future will not execute until the await is called.

As you can see Futures do not run in a thread, they are just polling futures. However we can use tokio tasks (which looks a lot like std lib threads) the execute the code on a thread

 
let handle = tokio::spawn(asyc move {
   execute().await;
}

handle.await.unwrap();

By default tokio executes on a threadpool but we could change things, as below

#[tokio::main(flavor = "current_thread")]

Which then uses time slicing instead of threads.

Tokio is good for non blocking IO, but tokio uses a single thread for it’s main event loop hence heavy CPU will basically slow down other tasks. Hence we would need to spawn threads as already discussed.

Slight detour

As a slight detour from async/await – tokio can also create “green” threads (lightweight threads from the runtime – not OS threads), for example

async fn execute() {
   time::sleep(time::Duration::from_secs(1)).await;
}

fn main() {
  let runtime = tokio::runtime::Runtime::new().unwrap();
  
  let future = execute();

  runtime.block_on(future);
}

Secure data in plain sight (using .NET)

Often we’ll come across situations where we need to put a password into our application. For example securing a connection string or login details for a services.

This post is more a dissection of the post Encrypting Passwords in a .NET app.config File than anything new, but interestingly through up a bunch of things to look at.

String & SecureString

Let’s start by looking at String storage. Ultimately strings are stored in memory in clear text and the disposal of them is determined by the garbage collection, hence their lifespan is non-deterministic so we could dump the strings using WinDbg or similar tools in clear text.

SecureString is part of the System.Security assembly and supplies a mechanism for encrypting a string in memory and also, when no longer required, can be disposed of.

The following code shows how we might use a SecureString.

public static class Secure
{
   private static byte[] ENTROPY = System.Text.Encoding.Unicode.GetBytes("Salt Is Not A Password");

   public static string Encrypt(SecureString input)
   {
      return Encrypt(ToInsecureString(input));
   }

   public static string Encrypt(string input)
   {
      var encryptedData = System.Security.Cryptography.ProtectedData.Protect(
         System.Text.Encoding.Unicode.GetBytes(input),
         ENTROPY,
         System.Security.Cryptography.DataProtectionScope.CurrentUser);
      return Convert.ToBase64String(encryptedData);
   }

   public static SecureString DecryptToSecureString(string encryptedData)
   {
      var result = DecryptToString(encryptedData);
      return result != null ? ToSecureString(result) : new SecureString();
   }

   public static string DecryptToString(string encryptedData)
   {
      try
      {
         var decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
            Convert.FromBase64String(encryptedData),
            ENTROPY,
            System.Security.Cryptography.DataProtectionScope.CurrentUser);
         return System.Text.Encoding.Unicode.GetString(decryptedData);
      }
      catch
      {
         return null;
      }
   }

   public static SecureString ToSecureString(string input)
   {
      var secure = new SecureString();
      foreach (var c in input)
      {
         secure.AppendChar(c);
      }
      secure.MakeReadOnly();
      return secure;
   }

   public static string ToInsecureString(SecureString input)
   {
      string returnValue;
      var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
      try
      {
         returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
      }
      finally
      {
         System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
      }
      return returnValue;
   }
}

References

Encrypting Passwords in a .NET app.config File
SecureString Class

Lazy loading Blazor WebAssembly assemblies

We can set our WebAssembly application to lazy load assemblies thus reducing the initial download of files.

For example if you have parts of your application that are rarely used, you could ensure these parts exist within their own (or shared) assembly and then set the project up to lazy load them when required. We simply make these changes to the csproj file

<ItemGroup>
  <BlazorWebAssemblyLazyLoad Include="your-assembly.dll" />
</ItemGroup>

We can use the LazyAssemblyLoader service to download/load assemblies as required (don’t forget to register the LazyAssemblyLoader as a singleton), for example

@inject LazyAssemblyLoader AssemblyLoader

@code {
  private async Task OnNavigateAsync(NavigationContext args)
  {
    try 
    { 
       if (args.Path = = "{PATH}") 
       { 
         var assemblies = await AssemblyLoader.LoadAssembliesAsync( 
           new[] { {LIST OF ASSEMBLIES} }); } 
       } 
       catch (Exception ex) 
       { 
         Logger.LogError(" Error: {Message}", ex.Message); 
       } 
    }
  }
}

Classes in Haskell (hint they’re more like interfaces)

A class (also know as typeclass) in Haskell is more analogous to an interfaces in C# and Java.

Let’s look at a simple class which will supply a toString function

class ToString a where 
  toString :: a -> String

This class allows us to use a toString function on different types. So think of this as an interface where data types that support it will now work with the toString function.

Let’s just create a simple data type as an example

data Point = Point { x :: Integer, y :: Integer }

Now it’d be cool if we could use code such as print (toString pt) and have the output a string like this “X:1, Y:2”. All we need to do is create an instance of the class. Before we do this, we’re going to need to also support converting of an Integer (as it’s used in the Point) to a string, so we may as well create an instance of the ToString class for Integer’s first and then use this code in our Point instance

instance ToString Integer where    
    toString i = show i

So now we’ve got a ToString to handle Integer’s we can combine to create an instance for a Point, like this

instance ToString Point where    
    toString pt = "X:" ++ toString (x pt) ++ ", " ++ "Y:" ++ toString (y pt)