Web3 with Svelte and Moralis
Tags: svelte, web3, moralis, javascript
Published: 2022-04-05 11:26:07

The goal of this app is to create a website where you can **Sign In with an Ethereum Wallet using MetaMask**, and **create a profile**. This should cover enough of how to use Svelte and Moralis so you can use as a starting point for your app.

#### Why Svelte (and not React)

[Svelte](https://svelte.dev/) is a Javascript framework for people who love HTML and CSS. React moves everything inside Javascript, making things unnecessarily complex, forcing you to re-learn how to do basic things. Svelte is much easier to understand, requires less code, and feels more like an extension of Javascript.

#### Why Moralis

[Moralis](https://moralis.io/) is a Backend-as-a-service that also gives you simple APIs to work with multiple chains. Authentication, Database, Storage and Cloud functions are part of what you get out of the box. Having all that on the same place makes things faster to setup and develop.

---

## Setting up the project

Let's jump straight into terminal and start a new [SvelteKit](https://kit.svelte.dev/) project.

```
$ npm init svelte@next web3-starter
$ cd web3-starter
$ npx svelte-add tailwindcss
$ npm install
$ npm run dev -- --open
```

1. If you don't have NPM installed [read this](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
2. Adding [Tailwind](https://tailwindcss.com/) is optional of course, but I enjoy using it. I won't focus on styles to keep things brief.


## Adding Moralis

You'll need to create an account on Moralis, then create a server. Add your server details to a `.env.development` file on the root of your project.

``` js
VITE_PUBLIC_MORALIS_APP_ID="paste your app id here"
VITE_PUBLIC_MORALIS_SERVER_URL="paste your server url here"
```

On terminal, stop the server with `ctrl + c` then restart it with `$ npm run dev` to pick up these variables.

Then add Moralis scripts to the head of your `app.html`.

``` html
  <!-- Moralis -->
  <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
  <script src="https://unpkg.com/moralis/dist/moralis.js"></script>
```

Now to configure Moralis, go to `__layout.svelte`. We'll add a function to get our `.env` variables and start Moralis, then call this function when the initial layout is Mounted (loaded), then load the rest of the app once Moralis has started.

``` html
<script>
  import "../app.css";
  import { onMount } from 'svelte';

  let moralisStarted = false; // track if started

  function configureMoralis() {
    // Get Env variables
    const serverUrl = import.meta.env.VITE_PUBLIC_MORALIS_SERVER_URL;
    const appId = import.meta.env.VITE_PUBLIC_MORALIS_APP_ID;
    // Start Moralis
    Moralis.start({ serverUrl, appId });
    // Let the app know it's started
    moralisStarted = true;
  }

  // Call configuration function when mounted
  onMount(() => {
    configureMoralis();
  });
</script>

<!-- If started, page content is loaded in this slot -->
{#if moralisStarted}
  <slot />
{/if}
```

## Using Svelte Stores

Svelte Stores are used to save information that you'll need to access from multiple areas of your application.

We'll create a `currentUser` store to track who's the current user logged in to our app.

Information on the User table in Moralis can only be accessed by the user themselves for privacy and security reasons. So we'll create a public profile table on our database to add public user details and use a Store to track that too.

```js
// new file: /src/lib/stores.js

import { writable } from "svelte/store";

export const currentUser = writable('loading');
export const currentProfile = writable('loading');
```

These don't look like much, but they're super powerful. We can import these stores and access their values in any part of our application with a `$` before the store name (eg: `$currentUser`), that way they update the UI automatically when values change.

I'm initializing both stores with the value `'loading'` so we know if no data was found or if we're still waiting for data to load.

## Sign in with Wallet

Time to create our Sign in button in `/src/lib/ButtonSignIn.svelte`

``` html
<script>
  import { currentUser, currentProfile } from "$lib/stores";

  // Sign in function
  async function handleSignIn() {
    if ($currentUser) return;

    // Authenticate with MetaMask (Moralis API)
    const user = await Moralis.authenticate({
      signingMessage: "Sign in with Ethereum"
    });

    // Update value of current user store
    $currentUser = user;
  }

  // Sign out function
  async function handleSignOut() {
    await Moralis.User.logOut();
    $currentUser = null;
    $currentProfile = null;
  }
</script>

<!-- Still waiting for user data -->
{#if $currentUser == 'loading'}
  <p>Loading user…</p>

<!-- User is logged in, check for profile -->
{:else if $currentUser}

  <!-- profile still loading -->
  {#if $currentProfile == 'loading'}
    <p>Loading profile…</p>

  <!-- has a profile -->
  {:else if $currentProfile}
    <p>User is {$currentProfile.get("username")}</p>
    <button on:click={handleSignOut}>Sign out</button>

  <!-- no profile found -->
  {:else}
    <p>Create profile (To do…)</p>
  {/if}

<!-- User is logged out, show Sign in button-->
{:else}
  <button on:click={handleSignIn}>
    Sign in with MetaMask
  </button>
{/if}
```

Here we're covering multiple states. We first look for the current user, then we look for that user's profile. If we have both, everything is fine and the user is fully logged in.

But we're stuck on loading. We still need to ask Moralis who's the current user (if there is one) when the page loads to know if we should show the log in button. This is something we need to happen on every page, so it should go on `__layout.svelte`.

``` html
<script>
  import "../app.css";
  import { onMount } from 'svelte';

  // Import stores
  import { currentUser, currentProfile } from "$lib/stores";

  // Import our sign in button
  import ButtonSignIn from "$lib/ButtonSignIn.svelte";

  let moralisStarted = false;

  // Get user when moralisStarted changes
  $: if (moralisStarted) getCurrentUser();

  // Get profile when value of $currentUser changes
  $: if ($currentUser) getCurrentProfile();

  async function configureMoralis() {...}

  // Check Moralis for current user
  async function getCurrentUser() {
    let user = Moralis.User.current();
    $currentUser = user;
    $currentProfile = 'loading';
  }

  // Check Moralis for current profile
  async function getCurrentProfile() {
    if ($currentUser == 'loading') return; // Ignore if still loading

    // Get Current Profile ID from Current User
    let id = $currentUser?.get("currentProfile")?.id;
    if (id) {

      // Get Profile from Moralis and update store
      const query = new Moralis.Query("Profile");
      $currentProfile = await query.get(id); // Profile or null

    } else {
      $currentProfile = null; // No profile created yet
    }
  }

  ...
</script>

{#if moralisStarted}

  <!-- Add sign in to top of the page -->
  <ButtonSignIn />
  <hr />

  <!-- Content for index.svelte and other pages will load here -->
  <slot />

{/if}
```

The `$:` indicates a [reactive statement](https://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive) in Svelte. It means the piece of code will run any time values change.

## Creating a profile

Now we need to let the user create a profile after signing in. So let's create a form for that in a new file `/src/lib/ProfileCreate.svelte`.

``` html
<script>
  import { currentUser, currentProfile } from "$lib/stores";

  // Any data we want in our profile
  let profileData = {
    username: "",
    bio: ""
  };

  async function handleCreateProfile() {

    // Get reference to Profile object from Moralis
    const Profile = Moralis.Object.extend("Profile");

    // Create a new profile object
    let profile = new Profile();

    // Add data to profile object
    profile.set("username", profileData.username.toLowerCase());
    profile.set("bio", bio);
    profile.set("user", $currentUser);

    // Save profile in Moralis
    await profile.save();

    // Update profile store
    $currentProfile = profile;

    // Save current profile on currentUser in Moralis
    $currentUser.set("currentProfile", profile);
    await $currentUser.save();
  }
</script>

<p>
  Welcome, {$currentUser.get("ethAddress")}. <br />
  Please create a profile.
</p>
<p>
  <input type="text"
    bind:value={profileData.username}
    placeholder="Username"
  />
</p>
<p>
  <textarea
    bind:value={profileData.username}
    placeholder="Bio (optional)"
  />
</p>
<p>
  <button
    on:click={handleCreateProfile}
    disabled={!profileData.username}
  >
    Create profile
  </button>
</p>
```

Svelte's data binding on the inputs mean the `profileData` variable is always in sync with what the user has typed in without any extra work. We're also disabling the button until the user adds some username.

## Show profile creation

Now back in the ButtonSignIn component, we'll show the profile creation form when user is logged in but no profile has been found.

``` html
<script>
  import { currentUser, currentProfile } from "$lib/stores";

  // Import profile create form
  import ProfileCreate from "$lib/ProfileCreate.svelte";

  ...
</script>

{#if $currentUser == 'loading'}
  ...

{:else if $currentUser}

  {#if $currentProfile == 'loading'}
    ...

  {:else if $currentProfile}
    ...

  <!-- no profile found -->
  {:else}

    <!-- show create profile form -->
    <ProfileCreate />

  {/if}

{:else}
  ...
{/if}
 
```

---

This already covers a lot of concepts you can use for adding more features. To add new tables and read data copy what we did for Profiles. To access data across your app create a new store. 

Check out Svelte and Moralis docs for other details.

Leave a comment if something was unclear, you have questions, or with what you're trying to build and I can add a part 2 to this article. Follow me here or on [Twitter](https://twitter.com/fabiogiolito) to see when I post new articles. 👋