loading...
Cover image for Angular, Rust, WebAssembly, Node.js, Serverless, and... the NEW Azure Static Web Apps! πŸŽ‰
Microsoft Azure

Angular, Rust, WebAssembly, Node.js, Serverless, and... the NEW Azure Static Web Apps! πŸŽ‰

wassimchegham profile image Wassim Chegham ・6 min read

In this guide, we are going to see how to take a front-end Angular app, a backend API written in Rust and compiled to Web Assembly, and deploy everything to a Serverless environment on the NEW Azure Static Web Apps service.

While the UI is written with Angular. The interesting part about this app is that the generator, the core part of the backend API, is entirely written in Rust, and then compiled to Web Assembly (or WASM for short). The public API is exposed behind a Node.js façade through a serverless Azure Function.

Let's start...

What are we creating?

We are going to build a Cat names generator app. I love cats, I bet you do too. This app allows you to discover unique cat names for your loved pet.

⚑️ Try the app LIVE at https://catsify.app ⚑️

Our app structure is the following (showing only the important parts):

.
β”œβ”€β”€ api
β”‚   β”œβ”€β”€ dist
β”‚   β”‚   └── func
β”‚   β”œβ”€β”€ func
β”‚   β”œβ”€β”€ ...
β”‚   β”œβ”€β”€ Cargo.toml
β”‚   └── rust
β”œβ”€β”€ app
β”‚   β”œβ”€β”€ dist
β”‚   β”‚   └── ui
β”‚   β”œβ”€β”€ ...
β”‚   └── src
β”œβ”€β”€ ...
└── scripts

Some highlights:

  • api is a standard Azure Functions App folder.
  • api/func contains the Node.js serverless function.
  • api/rust contains the Rust source code.
  • app contains the Angular source code.

And...

  • api/dist/func contains the API build artifacts.
  • app/dist/ui contains the APP build artifacts.

We will next describe the role of each stack: Rust/WASM, Node.js. Angular; and then explain how each part is built and deployed.

How are we creating it?

Azure Functions: The Node.js Code

Our public backend API is a Node.js Azure Function that acts as a Façade.

In the ./api/func/index.ts file, we simply import and invoke "a" generate() function (see next section), get and send back the result to the client.

Here is a simplified version of the code:

const { generate } = require("./wasm_loader");

const func = async function (context, req) {
   const name = await generate();
   const [adjective, noun] = name.split(" ");
   context.res = {
     body: {
       adjective,
       noun,
     },
   };
};

export default func;

However, in the ./api/func/wasm_loader.ts file, this is where the magic happens, we actually load the WASM module compiled from Rust (see the Rust story), invokes the generate_name_str function, passing in a seed argument, and decode the resulted string output.

Here is a simplified version of the code:

const fs = require('fs');
const path = require('path');

// the WASM file is copied to dis/func during the build
const wasmFile = path.join(__dirname, 'generator.wasm');

// a bunch of utilities to decode the WASM binary
function getInt32Memory(wasm) {...}
function getUint8Memory(wasm) {...}
function getStringFromWasm(wasm, ptr, len) {...}

// export a JavaScript function 
export const generate = async function() {

  // load the WASM module
  const bytes = new Uint8Array(fs.readFileSync(wasmFile));
  const result = await WebAssembly.instantiate(bytes);
  const wasm = await Promise.resolve(result.instance.exports);

  // setup args
  const retptr = 8;
  const seed = Date.now() % 1000 | 0;

  // invoke the WASM code
  const ret = wasm.generate_name_str(retptr, seed);

  // decode result
  const memi32 = getInt32Memory(wasm);
  const v0 = getStringFromWasm(...);
  wasm.__wbindgen_free(...);

  // this is final the decoded name
  return v0;
};

The core API: The Rust Code

This is NOT a deep dive guide into Rust code or WASM. You can learn about Rust and WASM on the official Rust website.

As I mentioned earlier, the main part of the backend API is the names generator which is written in Rust.

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn generate_name_str(seed: i32) -> String {
    // the seed is coming from the JS side
    let a = seed % (ADJECTIVES.len() as i32);
    let b = seed % (NOUNS.len() as i32);
    [ADJECTIVES[a as usize].to_string(), " ".to_string(), NOUNS[b as usize].to_string()].join("")
}

const ADJECTIVES: [&str; 1116] = [
"aback",
"abaft",
//...
];
const NOUNS: [&str; 1201] = [
"abbey",
"abbie",
//...
];

// used for debugging only
pub fn main() {
    println!("{:?}", generate_name_str(1));
}

Without going into many details, we basically use two Rust vectors to store the nouns and adjectives of the cats, then we construct a string by choosing a random value from each vector. The generate_name_str function is exported from Rust to JavaScript using the #[wasm_bindgen] outer attribute. This will enable us to call this function from the JavaScript code, and pass in the seed argument.

The UI: The Angular Code

The Angular app was generated with Angular CLI version 10.0.0-next.4. A classic setup!

How are we building it?

Azure Function

Our Node.js Azure Function Node.js code is written in TypeScript, hence we are using tsc to transpile into JavaScript and output the result in the ./dist/func folder.

cd api
tsc # will create ./dist/func/

Rust to WASM

For the Rust code, in order to compile it and generate the WASM module, we use the [wasm-pack](https://github.com/rustwasm/wasm-pack):

cd api
wasm-pack build # will create ./pkg/

And the following configuration in api/Cargo.toml:

[dependencies]
wasm-bindgen = "0.2.58"

[lib]
crate-type = ["cdylib", "rlib"]
path = "rust/lib.rs"

[[bin]]
name = "generator"
path = "rust/lib.rs"

[profile.release]
lto = true
panic = "abort"
# Tell `rustc` to optimize for small code size.
opt-level = "s"

Note: wasm-pack generates a Node.js wrapper that loads the WASM module. Since we wrote our own loader, we are only interested in the *.wasm file. We will then need to copy the WASM file to the ./api/dist/func:

cp pkg/catsify_bg.wasm dist/func/generator.wasm

The Angular build

Building the Angular app is straightforward. The Angular CLI takes care of everything:

ng build --prod

This command will generate the app bundle under ./app/dist/ui.

Project Build Recap

cd api
tsc                # builds the Azure function.
wasm-pack build    # builds the WASM module.
cp pkg/catsify_bg.wasm \
   dist/func/generator.wasm
cd ../app
ng build --prod    # builds the front-end app.

Now that we have created a front-end Angular app, and a backend serverless API, what's the easiest to static serverless app on Azure?

Introducing: Azure Static Web Apps! πŸŽ‰

Static Web Apps is a new offering from Azure App Service. It's a new, simplified hosting option for modern web apps powered by serverless APIs.

Alt Text

Static Web Apps offers:

  • Free web hosting for static content like HTML, CSS, JavaScript, and images.
  • Integrated API support provided by Azure Functions.
  • First-party GitHub integration where repository changes trigger builds and deployments.
  • Globally distributed static content, putting content closer to your users.
  • Free SSL certificates, which are automatically renewed.
  • Custom domains to provide branded customizations to your app.
  • Seamless security model with a reverse-proxy when calling APIs, which requires no CORS configuration.
  • Authentication provider integrations with Azure Active Directory, Facebook, Google, GitHub, and Twitter.
  • Customizable authorization role definition and assignments.
  • Back-end routing rules enabling full control over the content and routes you serve.
  • Generated staging versions powered by pull requests enabling preview versions of your site before publishing.

Let's deploy our app, in 3 steps!

Connect the GitHub account

Alt Text

Provide the build information

Alt Text

Watch GitHub build and Deploy the app on Azure Static Web Apps

Alt Text

Note: The YML workflow file created by Azure Static Web Apps has been updated with custom build steps.

Try it

Resources

GitHub logo manekinekko / catsify

Catsify is a simple and creative app that helps you to find a unique name for your lovely cat

Azure Static Web Apps CI/CD

What is Catsify?

Catsify is a Cat names generator, hosted on Azure Static Web Apps. The tech stack consists of:

  • An UI written in Angular v10 (preview).
  • An API written in Rust, compiled to WASM and exposed through a Node.js serverless Function.

Posted on May 19 by:

wassimchegham profile

Wassim Chegham

@wassimchegham

Angular and Bazel contributor β˜… Senior Developer Advocate at Microsoft β˜… Creator of xlayers.dev, ngx.tools, nitr.ooo, autocap.cc, hexa.run, async-await.xyz β˜… GDE for Google

Microsoft Azure

Any language. Any platform.

Discussion

markdown guide