Hello World

Let's build your first application (canister) with Azle!

Before embarking please ensure you've followed all of the installation instructions, especially noting the build dependencies.

We'll build a simple Hello World canister that shows the basics of importing Azle, exposing a query method, exposing an update method, and storing some state in a global variable. We'll then interact with it from the command line and from our web browser.

Quick Start

We are going to use the Azle new command which creates a simple example project.

First use the new command to create a new project called azle_hello_world:

npx azle new azle_hello_world

Now let's go inside of our project:

cd azle_hello_world

We should install Azle and all of its dependencies:

npm install

Start up your local replica:

dfx start

In another terminal, deploy your canister:

dfx deploy azle_hello_world

Call the setMessage method:

dfx canister call azle_hello_world setMessage '("Hello world!")'

Call the getMessage method:

dfx canister call azle_hello_world getMessage

If you run into an error during deployment, see the common deployment issues section.

See the official azle_hello_world example for more information.

Methodical start

The project directory and file structure

Assuming you're starting completely from scratch, run these commands to setup your project's directory and file structure:

mkdir azle_hello_world
cd azle_hello_world

mkdir src

touch src/index.ts
touch tsconfig.json
touch dfx.json

Now install Azle, which will create your package.json and package-lock.json files:

npm install azle

Open up azle_hello_world in your text editor (we recommend VS Code).

index.ts

Here's the main code of the project, which you should put in the azle_hello_world/src/index.ts file of your canister:

import { Canister, query, text, update, Void } from 'azle/experimental';

// This is a global variable that is stored on the heap
let message = '';

export default Canister({
    // Query calls complete quickly because they do not go through consensus
    getMessage: query([], text, () => {
        return message;
    }),
    // Update calls take a few seconds to complete
    // This is because they persist state changes and go through consensus
    setMessage: update([text], Void, (newMessage) => {
        message = newMessage; // This change will be persisted
    })
});

Let's discuss each section of the code.

import { Canister, query, text, update, Void } from 'azle/experimental';

The code starts off by importing Canister, query, text, update and Void from azle. The azle module provides most of the Internet Computer (IC) APIs for your canister.

// This is a global variable that is stored on the heap
let message = '';

We have created a global variable to store the state of our application. This variable is in scope to all of the functions defined in this module. We have set it equal to an empty string.

export default Canister({
    ...
});

The Canister function allows us to export our canister's definition to the Azle IC environment.

// Query calls complete quickly because they do not go through consensus
getMessage: query([], text, () => {
    return message;
}),

We are exposing a canister query method here. This method simply returns our global message variable. We use a CandidType object called text to instruct Azle to encode the return value as a Candid text value. When query methods are called they execute quickly because they do not have to go through consensus.

// Update calls take a few seconds to complete
// This is because they persist state changes and go through consensus
setMessage: update([text], Void, (newMessage) => {
    message = newMessage; // This change will be persisted
});

We are exposing an update method here. This method accepts a string from the caller and will store it in our global message variable. We use a CandidType object called text to instruct Azle to decode the newMessage parameter from a Candid text value to a JavaScript string value. Azle will infer the TypeScript type for newMessage. We use a CandidType object called Void to instruct Azle to encode the return value as the absence of a Candid value.

When update methods are called they take a few seconds to complete. This is because they persist changes and go through consensus. A majority of nodes in a subnet must agree on all state changes introduced in calls to update methods.

That's it! We've created a very simple getter/setter Hello World application. But no Hello World project is complete without actually yelling Hello world!

To do that, we'll need to setup the rest of our project.

tsconfig.json

Create the following in azle_hello_world/tsconfig.json:

{
    "compilerOptions": {
        "strict": true,
        "target": "ES2020",
        "moduleResolution": "node",
        "allowJs": true,
        "outDir": "HACK_BECAUSE_OF_ALLOW_JS"
    }
}

dfx.json

Create the following in azle_hello_world/dfx.json:

{
    "canisters": {
        "azle_hello_world": {
            "type": "custom",
            "main": "src/index.ts",
            "candid": "src/index.did",
            "build": "node_modules/.bin/azle compile azle_hello_world",
            "wasm": ".azle/azle_hello_world/azle_hello_world.wasm",
            "gzip": true
        }
    }
}

Local deployment

Let's deploy to our local replica.

First startup the replica:

dfx start --background

Then deploy the canister:

dfx deploy

Common deployment issues

If you run into an error during deployment, see the common deployment issues section.

Interacting with your canister from the command line

Once we've deployed we can ask for our message:

dfx canister call azle_hello_world getMessage

We should see ("") representing an empty message.

Now let's yell Hello World!:

dfx canister call azle_hello_world setMessage '("Hello World!")'

Retrieve the message:

dfx canister call azle_hello_world getMessage

We should see ("Hello World!").

Interacting with your canister from the web UI

After deploying your canister, you should see output similar to the following in your terminal:

Deployed canisters.
URLs:
  Backend canister via Candid interface:
    azle_hello_world: http://127.0.0.1:8000/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai&id=rrkah-fqaaa-aaaaa-aaaaq-cai

Open up http://127.0.0.1:8000/?canisterId=ryjl3-tyaaa-aaaaa-aaaba-cai&id=rrkah-fqaaa-aaaaa-aaaaq-cai or the equivalent URL from your terminal to access the web UI and interact with your canister.