ijxkc-rqaaa-aaaal-qcdta-cai.icp0.io Open in urlscan Pro
2a0b:21c0:b002:2:5000:afff:fee6:a836  Public Scan

URL: https://ijxkc-rqaaa-aaaal-qcdta-cai.icp0.io/blog/codelab-03/index.html
Submission: On December 11 via api from US — Scanned from FR

Form analysis 0 forms found in the DOM

Text Content

On IC Academy

How to Implement a Blog-View Counter using Astro, Svelte on the Internet
Computer!
April 24, 2024 - written by Roland BOLE (Instructor)
In this article a practical scenario is explored. Consider having a static
website hosted on the Internet Computer (IC), and you wish to implement a view
counter for each blog post. While this might appear straightforward to a
traditional Web 2 developer, the question arises how this will be accomplished
within Web 3 on the IC.
All Posts
Code Lab
233



👋 LET’S FIND IT OUT.

First, allow me to demonstrate how I have addressed the issue. The following
illustration provides a framework for the set up. By the way, this method is
applied for the blog you are currently reading.

Illustration of the context


THE ASTRO FRONTEND

Astro, as a static site generator (SSG), outputs fully-prepared HTML sites with
minimal client-side JavaScript involvement. As the website is static rather than
a Single-Page Application (SPA), it is necessary to devise a straightforward and
user-friendly method for interfacing with a backend hosted on a canister on the
IC.

To facilitate it, JavaScript is required to establish a secure connection. More
technically speaking, a JavaScript Agent from @dfinity/agent is needed to manage
the connection. A simple JSON request doesn’t currently work.

Here, Svelte becomes relevant. Utilising a small and compact Svelte component
with approx. 87 KB, you can adopt a familiar and straightforward method to
establish a connection with the IC. While Vanilla JS is also an option, Svelte
was chosen for this example. Svelte is very widespread among developers and you
could use even more Svelte specific features.

The presented Svelte component is fully equipped to retrieve the view count and
perform a simple animation once the data is loaded. On a blog details page the
component also increments the page view as well, with a dedicated update call to
the backend canister. Since you use the same component for both situations, this
behaviour is parameterized. This enables the use of this Svelte component
anywhere on the Astro website. Whether in the detailed view of a blog post or
within the card list view of all blog posts, the component functions
independently. Also every request to the backend is handled autonomously through
the Actor model for concurrent and asynchronous computation.

What is an actor?

An actor is a primitive in the actor model. It is a process with an encapsulated
state that communicates with other concurrently running actors through
asynchronous messages that are received sequentially. In the context of the IC,
actors are relevant because canisters on the IC (a type of smart contract)
follow the actor model for concurrent and asynchronous computation.

Actors can modify their own private state, but can only alter other actors
indirectly through messages. They are a part of the actor model which is used by
canisters for concurrent and asynchronous computation.

In the Motoko programming language an actor is written in code and defines
public shared functions that can be accessed from outside the IC. A client, like
a laptop or a mobile phone, can send a request over the internet to call one of
the public functions defined in an actor.

This is how the view counter looks like.

To maintain optimal website performance, the Svelte component queries the data
immediately before entering the viewport. Consequently, page views for e.g. card
elements not currently visible on the frontend are not queried.

To determine when the component should issue a request to the IC, the JavaScript
function getBoundingClientRect() is used. This method returns a DOMRect object,
which provides details about an element’s size and its position relative to the
viewport. Using this information, you can calculate the optimal time for a
component to execute a request to the IC.

âš¡ More on that you can find here on MDN Web Docs
The getBoundingClientRect() method returns a DOMRect object providing
information about the size of an element and its position relative to the
viewport.
developer.mozilla.org


This Svelte integration can be implemented across the Astro islands, where an
island represents any interactive UI component on the page. Envision an island
as an interactive widget surrounded by a sea of static, lightweight,
server-rendered HTML. Read more on that interesting approach in a post by Preact
creator Jason Miller on August 11, 2020.

The final frontend code required to fetch the number of blog views is quite
concise and can be summarised in a single line as shown below. The line utilises
the backend object derived from the ready-to-use JavaScript actor represented
through the backend object which is automatically generated by the dfx generate
command. A few more lines of code and the magic of Svelte will do the rest 😀.

const result = await backend.getBlogMetric(pageId);
  if(result[0]){
    pageView = result[0].view;
  }


Setting up the JavaScript agent to connect to the backend canister requires a
bit more work. Once that’s done, executing a function independent of the query
or update function is really easy, as you can see above.

At this point, I would like to take this opportunity to once again point out our
StarterKits which can be used to easily study the connection between frontend
and backend.

âš¡ Starter Kits
Start your dApp development journey on the Internet Computer today with our
ready to use StarterKits and be part of shaping the internet of tomorrow.
blog.icacademy.at


Next, have a deeper look at the backend. While the backend code is somewhat more
sophisticated, it remains manageable.


THE BACKEND

For the backend, Motoko is used as a language. Motoko is the native language of
the IC. However, you can use other languages as well like Rust, TypeScript or
Python for the same use case.

Conversely, on the backend side, there is a requirement for a mechanism to
record the view count of each page, alongside additional metric data, and
potentially other information in the future. See the type Metric later in this
article.

Below is the whole dedicated Motoko code. The stable Map library by Zhenya
Usenko — credits to him — is used, available for installation at mops.one/map to
store the metric data in a canister update resistant way. The rest is pretty
straightforward. There are two functions: getBlogMetric and incrementViewCount.

The function getBlogMetric accepts a pageId in text format and returns a Result
object containing the page metric. Similarly, the function incrementViewCount
also takes a pageId, retrieves the existing view count for the page and
increments it if it exists: If not, it creates a new entry for that specific
pageId in the store. In either case, the updated page view count is returned.

import Text "mo:base/Text";
import T "./types";
import Map "mo:map/Map";
import { thash } "mo:map/Map";

actor BlogBackendService { 
  // The map of metrics.
  stable let map = Map.new<Text, T.Metric>();

  // Get the metric for a pageId.
  public query func getBlogMetric(pageId : Text) : async ?T.Metric {
    return Map.get(map, thash, pageId);
  };

  // Increment the view count of a pageId.
  public func incrementViewCount(pageId : Text) : async T.Result {
    switch (Map.get(map, thash, pageId)) {
      // If there is no entry for the id, create a new one.
      case null {
        let newMetric:T.Metric = {view = 1};        
        Map.set(map, thash, pageId, newMetric);
        return #ok newMetric;
      };
       // If there is an entry for the id, inc the view count + 1
      case (?metric){
        let newMetric:T.Metric = {view = metric.view + 1};
        Map.set(map, thash, pageId, newMetric);
        return #ok newMetric;
      };
    };  
  };
};


For the used types see: types.mo

import Nat "mo:base/Nat";
module Types {
  //The result type of given pageId
  public type Result = {
    #ok: Metric;
    #err;
  };

  // The main store type of the service.
  public type Metric = {
    view: Nat;
  };
};


Candid:service visibility

A brief aside here: When deploying a canister like this on the IC mainnet, you
can review and execute all public methods via the IC dashboard.

If you select a random canister, you might be able to see all public methods and
have the opportunity to invoke the functions. This is possible because the
candid:service metadata section in your dfx.json file is public by default.

This issue can be addressed by simply adding the following lines (note the
metadata object) to the corresponding canister section in the dfx.json file.

"backend": {
  "main": "backend/main.mo",
  "type": "motoko",
  "metadata": [
     {
       "name": "candid:service",
       "visibility": "private"
      }
   ]
}



WRAP UP

As demonstrated, a compelling solution emerges from the integration of various
techniques. This example shows how a static website operated on the IC can be
expanded to include dynamic content. It shows a way to address a common Web 2
development task and transfer it on the IC.

Note that in this scenario both the frontend and backend code are executed as a
smart contract on the IC blockchain in a decentralised way. A dedicated firewall
or similar protective measures are not required.

As no authentication method is used to protect the view counter, this approach
is not 100% secure. However, for this use case it is secure enough.

> Now you know a bit more about dynamic content on the IC, happy coding. 🚀

Don’t forget to register to our newsletter and we’ll keep you up to date on
similar exciting topics related to the use of the Internet Computer.

Newer Post
How We Celebrate the 3rd ICP Anniversary!
Older Post
March 2024 Newsletter
Menu
Where to go?
Home
Courses / Certifications
Consulting
Showcase
Blog
Free Quizzes
Newsletter
YouTube Playlists
About IC Academy
About
Bio
Legal
Imprint
Data Privacy

Join our community on OpenChat, a decentralized chat app governed by the people
for the people. OpenChat meets Slack in a decentralized package.
© 2024 IC Academy - Made with ♥ in Austria
powered by SDG - samlinux development