backgroundradial

Create web apps programmatically

Thanks to the new version of the Wasmer Javascript SDK: `@wasmer/sdk`, you can now create websites from Node.js or your browser easily

edoardo avatar
edoardo
Edoardo Marangoni

Software Engineer

sdk

August 13, 2024

arrowBack to articles

Today we're excited to announce significant updates to the Wasmer JS SDK @wasmer/sdk. With these latest enhancements, users can now programmatically create packages, publish them to the Wasmer registry, and deploy apps to Wasmer Edge… and more, all from Javascript, using your browser or Node!

We have created an awesome website to demonstrate the capabilities of the new SDK:

Wasmer JS SDK demo website

Lets get started!

Note: you can reproduce all the examples in the browser: sdk-demo-wasmer.wasmer.app, or using Node.js: github.com/wasmerio/wasmer-js/tree/main/examples/deploy-node

Initializing the library

Installing the library

$ npm install @wasmer/sdk # 0.7.1

The first step to programmatically create web apps is obtaining a token from the Wasmer registry. To generate a token, visit this page: Wasmer Access Tokens.

Once you have your token, you can import and initialize the Wasmer JS SDK in your code as follows:

import { init, Wasmer } from "@wasmer/sdk";

await init({ token: "<YOUR_TOKEN>" });

Note: If you have a Node version prior to 22 , you should instead import @wasmer/sdk/node

Deploying a static site programmatically

Let’s do a cool example: let’s create a simple website - a copy of the static-website template - just by specifying the same structure the usual manifest for Wasmer packages have, but as a plain JS value. Assuming the library is initialized, we start by declaring the manifest of our package:

const manifest = {
  command: [
    {
      module: "wasmer/static-web-server:webserver",
      name: "script",
      runner: "https://webc.org/runner/wasi",
      annotations: {
        wasi: {
          "main-args": [
            "--directory-listing=true", // This shows an index view of directories
          ],
        },
      },
    },
  ],
  dependencies: {
    "wasmer/static-web-server": "^1",
  },
  fs: {
    "/public": {
      "myfile.txt": "The contents",
      other: {
        "index.html": "Feels good being embedded here ᕕ(ᐛ)ᕗ",
      },
    },
  },
};

let pkg = await Wasmer.createPackage(manifest);

Now that we have the package set, we can proceed and just deploy it

const appConfig = {
  name: "my-static-website",
  owner: "<YOUR_USERNAME>",
  domains: ["my-awesome-site.wasmer.app"],
  package: pkg,
};

let result = await Wasmer.deployApp(appConfig);

console.log("\n== Deploying user-created app ==");
console.log("Deployed app id: ", result.id);
console.log("Deployed app unique url: ", result.url);

Et voilá! my-awesome-site.wasmer.app is now fully set up and serving your contents.

Static web server demo

See the specific example in Github

Deploying a self-contained PHP app

In this section we show a self contained example that users can run to deploy a simple PHP application running on Wasmer Edge.

import { init, Wasmer } from "@wasmer/sdk";

await init({token: "<YOUR_TOKEN>");

const manifest = {
  "command": [
    {
      "module": "php/php:php",
      "name": "run",
      "runner": "wasi",
      "annotations": {
        "wasi": {
          "main-args": [
            "-t",
            "/app",
            "-S",
            "localhost:8080"
          ]
        }
      }
    }
  ],
  "dependencies": {
      "php/php": "=8.3.401"
  },
  "fs": {
      "/app": {
          "index.php": "<?php phpinfo(); ?>"
      }
  }
};

let pkg = await Wasmer.createPackage(manifest);

const appConfig = {
    name: "my-php-app",
    owner: "<YOUR-USERNAME>",
    package: pkg,
    default: true
};

let result = await Wasmer.deployApp(appConfig);

console.log("\n== Deploying user-created app ==");
console.log("Deployed app id: ", result.id);
console.log("Deployed app url: ", result.url);

And there we go, now we have a PHP app deployed!

Deploying packages is not all that you can do in the new @wasmer/sdk, you can also create packages and run them directly in your browser, as shown in the following sections.


Creating and running packages

const manifest = {
  command: [
    {
      module: "wasmer/python:python",
      name: "myCommand",
      runner: "wasi",
      annotations: {
        wasi: {
          "main-args": ["/app/myapp.py"],
        },
      },
    },
  ],

  dependencies: {
    "wasmer/python": "3.12.5+build.7",
  },

  fs: {
    app: {
      "myapp.py": `print("Hello from Python!");`,
    },
  },
};

let pkg = await Wasmer.createPackage(manifest);

Attentive users will have already noticed an important peculiarity: when creating packages, the files we want to use in it, listed under fs, are embedded directly in the manifest itself.

This approach differs from the “usual” definition of package manifests used by the CLI, where you would specify the path to directories or files to include in the package. On the web, we can’t access filesystem nodes directly, so we need an alternative way to specify the contents of the package.

Web users can run their packages using the familiar mechanism available in previous versions of the SDK. However, Node users currently cannot run packages this way due to the lack of specific worker-related API calls.

let instance = await pkg.commands.myCommand.run();
const output = await instance.wait();

Publishing packages

The JS SDK allows users to publish packages created with a name and a version:

const manifest = {
  "package": {
    "name": "<OWNER>/<PACKAGE_NAME>",
    "version": "0.1.0",
  }
 ...
};

let pkg = await Wasmer.createPackage(manifest);

Once the package is created, publishing packages is just one call away!

await Wasmer.publishPackage(pkg);

Your package will then be available at the URL with the following structure: https://wasmer.io/<OWNER>/<PACKAGE_NAME>.


One last thing…

Big files

In the two examples we saw, files to be embedded in the package were represented using simple strings but this, of course, is not always handy. Another possibility is that of using Uint8Array to store the bytes of the file to be embedded:

...
"fs" : {
  "public": {
    "my_large_blob": new Uint8Array(...)
  }
}

File metadata

Users can specify metadata for each file in the following fashion:

...
"fs" : {
    "public": {
        "index_date.html": {data: "Hello, js!", modified: new Date()},
        "index_timestamp.html": { data: "Hello, js!", modified: 987656789 },
    }
}

Package metadata

Provide package metadata such as README.md and License as follows:

const manifest = {
  "package": {
    "readme": "This is my readme!",
    "license": { data: "This is my license!", modified: new Date() }
  },
  ...
}

Technical challenges

To deliver these updates to the SDK, we faced two major challenges. The first was enabling calls to a Wasmer-like registry on the web. The second was adapting libraries to build packages without relying on a filesystem. Both tasks have been successfully completed.

As a result, you can now utilize the wasmer-api crate on the web. More interestingly, we have been working on a new spec for containerizing apps at the Edge that we call webc. Stay tuned for more updates on this front!

Conclusions

This post exemplified how users can now programmatically create web applications with a minimal set of function calls. We can’t wait to see what you come up with! Show us your projects and ask us questions on our dedicated discord server.

About the Author

Edo is the resident compiler guy, and part of the CLI magic happens thanks to him as well

Edoardo Marangoni avatar
Edoardo Marangoni
Edoardo Marangoni

Software Engineer

Read more

bindingswasmer-gogosdk

Wasmer Go embedding 1.0 lift-off

Syrus AkbaryFebruary 26, 2021

wasmer edgejavascriptSpiderMonkey

Announcing WinterJS

Syrus AkbaryOctober 27, 2023

wasmer edgejavascriptWinterJS

How fast is WinterJS vs alternatives? Blazing-fast

Syrus AkbaryNovember 1, 2023

wasmer runtimejavascriptwasmer-jsbrowser

Introducing the new Wasmer JS SDK

Michael BryanDecember 13, 2023