Create a Crypto Converter with Svelte and Strapi

Shada - Nov 17 '21 - - Dev Community

Cryptocurrencies have been in use since 2009, when Bitcoin was officially launched. As a result of its acceptance over time, numerous other cryptocurrencies and tokens have been created and are in use.

Today, people have crypto portfolios where they hold different cryptocurrencies. There is often the need to know the value of one crypto against another. This need has given birth to cryptocurrency converters. Cryptocurrency converters are applications that convert the value of one crypto to another.

In this tutorial, we will build a simple cryptocurrency converter. It will consume external crypto APIs that return real-time cryptocurrency rates. It will be able to convert the value of one coin to another. An example would be converting Bitcoin to Ethereum. If the value of Bitcoin (BTC) is entered, it will calculate and give the equivalent value in Ethereum (ETH). Strapi will be used to build out the application's backend, and Svelte will be used for the front end. With that said, let's get started.

Goal

This tutorial has two sections.

Goals of the First Section

By the end of the first section, the reader should understand the following concepts: Strapi, Strapi collection types, Strapi routes, & Strapi controllers and methods, and be able to create the backend of our Cryptocurrency converter using JavaScript in our Strapi controller.

Goals of The Second Section

By the end of the second section, the reader should:

  • understand Svelte, its pros and cons,
  • be able to scaffold a new Svelte project using NPM,
  • understand Svelte components
  • and create the front end of our Cryptocurrency converter using Svelte.

Prerequisite

Readers should have a basic knowledge of HTML, CSS, JavaScript, Node.js, Yarn, and ReactJs or VueJs to follow along.

What is Strapi?

Strapi is an open-source headless CMS built with React and Node. It gives the developers the flexibility of using the technology of their choice in the front end of the application. With a good understanding of Node.js and the React framework, Strapi can be used to build anything. Let's get started building the backend of the application with Strapi.

Step 1 - Create a Strapi Project

To get started, let's create a new Strapi project using Yarn like so:

    yarn create strapi-app crypto-converter --quickstart
Enter fullscreen mode Exit fullscreen mode

The command above scaffolds a new Strapi project in your preferred directory. The --quickstart command makes the process even more seamless by handling databases and others

Note that NPM can also be used in place of Yarn. Once the installation is complete, you will be redirected to the page below and prompted to create an admin account needed to access the Strapi admin panel.

Strapi admin panel

After creating the account, you will be redirected to the dashboard like the one in the image below. You have now become the first admin to your Strapi project.

Strapi admin dashboard

Now we have created our admin user account, let’s proceed to creating our API in the next step below.

Crypto Converter API

Step 2 - Create a collection.

Now, we have created our project and an admin account to access the dashboard. Let’s dive into the main business of the day which is creating our crypto convert app. In our Strapi application, we need a Route and a Controller to keep stuff organized. Let’s create a collection type from our dashboard. To create a collection type, follow the steps outlined below on the Strapi admin panel:

  • From the side menu in the dashboard, click on Content-Types Builder
  • Click the "+ Create new collection type" link
  • Name it crypto-converter and click continue
  • Add a Text field (short text) and name it Name
  • Click the "Finish" button
  • Click the Save button

If you still need help with creating your collection type, check Strapi documentation.

Creating a collection type in Strapi goes ahead to modify the structure of the codebase of our Strapi project. In our case, this is what our API folder looks like

Strapi API structure

Step 3 - Create a route

The collection type we created in the API folder modified the files and folders into an MVC design pattern. Strapi was built this way. Collection types created from the admin dashboard generate new files and folder structures. Next, we need to create a new controller file and a route that will point to the controller where all of our logic and external API calls will be implemented.

Create controller

In Strapi, there are two ways of creating a controller. We can create a controller using the strapi generate command as we have below:

    strapi generate:controller my-crypto-converter
Enter fullscreen mode Exit fullscreen mode

or simply create a new JavaScript file in the controller folder (API/crypto-converter/controller). In our case, I chose the second option. I created a new JavaScript file in the project directory named my-crypto-converter.js .

In the newly created controller, let's add a new method like so.

    'use strict';
    /**
     * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services)
     * to customize this service
     */
    module.exports = {
        convert: async ctx => {
            await ctx.send({
                message: "controller configured"
            })
        },
    };
Enter fullscreen mode Exit fullscreen mode

Every Strapi controller must be an async and await JavaScript function like what we have above. The ctx.send() method is one of the many Strapi in-built methods. It is used to send responses in message form.

Let us proceed by creating our route. Navigate to the api/crypto-converter/config/routes.json file. You will find already generated routes for our collection type. We will add one more router link pointing to our newly created controller like so:

    {
          "method": "POST",
          "path": "/my-crypto-converter/convert",
          "handler": "my-crypto-converter.convert",
          "config": {
            "policies": []
          }
        } 
Enter fullscreen mode Exit fullscreen mode

The handler points to the controller and the method. my-crypto-converter is the controller while convert is the method in the controller we need to trigger its action. path points to the router path.

Step 4 - Grant public access to the route

At this point, we have successfully created our route and controller. We need to grant public access to the route. This will make it accessible publicly. To grant access, navigate to settings→users & permissions plugins→permissions→application and grant public access to the /my-crypto-converter/convert route.

For more details on setting roles and permissions in Strapi, check the documentation here.

Step 5 - Install Axios

Now we are good to go. We need to make API calls to an external crypto API endpoint (for real time exchange rates). There are plenty of ways we can achieve this but for this tutorial, we will use Axios. To install Axios in our app use the following command:

    yarn add Axios
Enter fullscreen mode Exit fullscreen mode

Step 6 - Create a Nomics.com API key and fetch rates using Axios

After installing Axios, we should be ready to make external API calls using it. We need a way to get up-to-date exchange rates of cryptocurrencies. For this, we will consume the nomics.com API. I choose it because of how easy it is to create an account and generate your API key. I have created my account and generated my API key. If you are coding along, you should do the same too.
Check the documentation for more details on creating your API key.

We will make an HTTP Get request to the endpoint below to get the exchange rate of any cryptocurrency against US dollars (USD).

https://api.nomics.com/v1/currencies/ticker?key=YOUR_API_KEY&ids=BTC,ETH,XRP,
Enter fullscreen mode Exit fullscreen mode

The result of the API call will enable us to calculate the rate of one crypto to another.

In my-crypto-converter.js modify the contents like so:

            'use strict';
        const axios = require('axios');
        /**
         * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-services)
         * to customize this service
         */
        module.exports = {
            convert: async ctx => {
                //post values
                const amount = ctx.request.body.amount;
                const baseCoin = ctx.request.body.baseCoin;
                const compareCoin = ctx.request.body.compareCoin;

                // fetch all rates in USD
                const baseCoinRate = await getRate(baseCoin);
                const compareCoinRate = await getRate(compareCoin);
                // get rate in crypto currency
                const exchange = await calculateExchange(baseCoinRate, compareCoinRate, amount);
                return exchange;
            },

        };
        //get exchange rate
        async function getRate(coin){
            //API key should be passed along as key in the URL below
            const { data } = await axios.get(`https://api.nomics.com/v1/currencies/ticker?key=169170*******************90&ids=${coin}`); 
            return data[0].price;

        }
        // calculate and return result
        async function calculateExchange (base, compare, amount){
            const rate =  await base/compare;
            const result = await rate * amount
            return result;
        }
    };
Enter fullscreen mode Exit fullscreen mode

In the code above, we did the following:

  • fetch coin values
  • calculation and conversion
  • return result

I’ll explain every step in detail. In the convert method in our controller, we accepted a few arguments coming from the Svelte app. These are amount, baseCoin, compareCoin.
baseCoin is the coin we have possession of and want to confirm its value against another. compareCoin is the currency we are comparing baseCoin value against, amount is the total amount to convert.

We can convert crypto against another by dividing the value of the baseCoin by the value of compareCoin and multiplying the result by the value of the amount. This was done in calculateExchange function above.

Fetch coin values
We created two additional functions getRate() and calculateExchange(). getRate() fetches the rate of a particular coin using Nomic API. It accepts a parameter coin. This is the short code of the coin which is passed along with the API call. The function returns the current exchange rate of the coin in question.

    // fetch all rates in USD
    const baseCoinRate = await getRate(baseCoin);
    const compareCoinRate = await getRate(compareCoin);

`calculateExchange()` does the calculation and logic. It returns the final result. This function accepts three parameters `baseCoinRate`, `compareCoinRate`, `amount`.  This snippet below shows how we used it.


    // calculate and return result
        async function calculateExchange (base, compare, amount){
            const rate =  await base/compare;
            const result = await rate * amount
            return result;
        }
Enter fullscreen mode Exit fullscreen mode

In convert , we called all the functions and return a response to our Svelte frontend. Let's proceed to create the Svelte app.

What is Svelte?

Svelte is a modern-day JavaScript compiler for creating reactive web applications and interfaces. It can be used in parts of an already existing application or in a new single-page application (SPA).

Svelte is very similar to other frontend JavaScript frameworks like React and VueJS but it uses a different approach that avoids a virtual DOM. Svelte compiles your HTML, CSS, and JavaScript into a single build file that can be deployed. This makes it a lot faster and provides a better user experience.

Why use Svelte?

  • Svelte is a compiler, not a framework. This simply means that Svelte is not shipped with dozens of framework script making it light weighted and easier to learn
  • Compiles code into a single vanilla JavaScript file ready for deployment. Deployment of the compiled file only, not the entire Svelte technology results in a faster deployment process and better performance of our application in production.

Cons

  • React, Vue, and the like are shipped with lots of features out of the box. This might not be the case with Svelte as you might have to do a lot of things yourself when using Svelte.

With that said, let’s proceed by creating the front end of our Cryptocurrency converter application using Svelte. The next step is to create a Svelte app.

Step 7 - Create a Svelte project

To get started, let's create our svelte app using the command below:

    npx degit sveltejs/template crypto-converter-svelte
    cd crypto-converter-svelte
    npm install
    npm run dev
Enter fullscreen mode Exit fullscreen mode

npx degit sveltejs/template crypto-converter-svelte scaffolds a Svelte in a directory named crypto-converter-svelte. cd crypto-converter-svelte navigates into the project directory of our newly created Svelte app. npm install installs all the dependencies in the package.json file of the svelte app needed for the app to function properly.

npm run dev starts the application in a local development environment making it available at localhost:5000. The application will automatically reload when we save any change.

Let’s take a closer look at the folder structure of the Svelte application:

moz-todo-svelte
├── README.md
├── package.json
├── package-lock.json
├── rollup.config.js
├── .gitignore
├── node_modules
├── public
│   ├── favicon.png
│   ├── index.html
│   ├── global.css
│   └── build
│       ├── bundle.css
│       ├── bundle.js
│       └── bundle.js.map
├── scripts
│   └── setupTypeScript.js
└── src
    ├── App.svelte
    └── main.js
Enter fullscreen mode Exit fullscreen mode

The main.js file is the main point of entry of the Svelte application. It imports the App.svelte component which is the main component of our Svelte application like you can see below.

    import App from './App.svelte';
    const app = new App({
        target: document.body,
        props: {
            name: 'Moses'
        }
    });
    export default App;
Enter fullscreen mode Exit fullscreen mode

Svelte is a component-based framework. You can think of Svelte components as building blocks of the application. It has a <script></script> section for writing JavaScript, a mark up section for HTML and a <style></style> for writing CSS. The following is the content of our App.svelte.

    <script>
      export let name;
    </script>
    <main>
      <h1>Hello {name}!</h1>
      <p>
        Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn
        how to build Svelte apps.
      </p>
    </main>
    <style>
      main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
      }
      h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
      }
      @media (min-width: 640px) {
        main {
          max-width: none;
        }
      }
    </style>
Enter fullscreen mode Exit fullscreen mode

Let's modify the content of App.svelte to build out our frontend. Replace the content of App.svelte with the code below.

    <script>
      import Modal, { getModal } from "./Modal.svelte";
      let amount;
      let baseCoin;
      let convertCoin;
      let result;
      function handleSubmit(e) {
        conversion(amount, baseCoin, convertCoin);
      }
      async function conversion(amount, baseCoin, convertCoin) {
        const call = await fetch(
          "http://localhost:1337/my-crypto-converter/convert",
          {
            method: "POST",
            body: JSON.stringify({
              baseCoin: baseCoin,
              compareCoin: convertCoin,
              amount: amount,
            }),
            headers: {
              "Content-type": "application/json; charset=UTF-8",
            },
          }
        );
        result = await call.json();
        //opens modal
        getModal().open();
      }
    </script>
    <div class="container">
      <h1>Crypto converter</h1>
      <form id="surveyForm" class="mt-4" on:submit|preventDefault={handleSubmit}>
        <div class="form-group">
          <label for="baseCoin">Choose base currency:</label>
          <select name="baseCoin" bind:value={baseCoin}>
            <option value="BTC">Bitcoin</option>
            <option value="ETH">Ethereum</option>
            <option value="XRP">Ripple</option>
            <option value="audi">Audi</option>
          </select>
        </div>
        <div class="form-group">
          <label for="compareCoin">Choose currency to convert to:</label>
          <select name="compareCoin" bind:value={convertCoin}>
            <option value="BTC">Bitcoin</option>
            <option value="ETH">Ethereum</option>
            <option value="XRP">Ripple</option>
            <option value="audi">Audi</option>
          </select>
        </div>
        <div class="form-group">
          <input
            type="num"
            class="form-control"
            placeholder="Enter amount in coin"
            bind:value={amount}
            required
          />
        </div>
        <button class="btn btn-full">Convert</button>
      </form>
      <Modal>
        <h4>Success!!</h4>
        <h3>
          <small>You will get</small>
          {result} <small>in</small>
          {convertCoin}
        </h3>
      </Modal>
    </div>
    <!-- the modal without an `id` -->
    <style>
      .container {
        max-width: 1200px;
        margin: 0 auto;
        margin-top: 8rem;
      }
      h2 {
        margin-top: 0;
      }
      .form-group > *,
      .btn-full {
        width: 100%;
      }
      .form-control,
      .btn-full {
        border-radius: 3px;
      }
      .submitted input:invalid {
        border: 1px solid #c00;
      }
      .submitted input:focus:invalid {
        outline: 1px solid #c00;
      }
      .error-alert {
        border: 1px solid #c00 !important;
        padding: 6px;
        text-align: center;
        color: #c00;
        border-radius: 3px;
      }
    </style>
Enter fullscreen mode Exit fullscreen mode

Let's go over the code above. In the main section, we added an HTML form to accept the values of the cryptocurrency we want to exchange and the amount. We used svelte bind:value={} to bind the form input to the variables used in the API call.

In the JavaScript section, we declared amount , baseCoin, convertCoin variables and made our API call to our Strapi backend.

Adding a Modal to our Svelte Application
Just to spice it up a little, we added a modal to display the response nicely. Check Svelte documentation for more on creating this simple modal.

getModal().open() function opens up the modal to display response from the API.

The body of the modal is contained in the code below.

    <Modal>
        <h4>Success!!</h4>
        <h3>
          <small>You will get</small>
          {result} <small>in</small>
          {convertCoin}
        </h3>
      </Modal>
Enter fullscreen mode Exit fullscreen mode

The modal component functionality is done in ./Modal.svelte component. We imported it using this import Modal, { getModal } from "./Modal.svelte";

In the SRC directory, create a Modal.svelte file and add the following code to create a modal.

    <script context="module" lang="ts">
      let onTop; //keeping track of which open modal is on top
      const modals = {}; //all modals get registered here for easy future access
      //  returns an object for the modal specified by `id`, which contains the API functions (`open` and `close` )
      export function getModal(id = "") {
        return modals[id];
      }
    </script>
    <script lang="ts">
      import { onDestroy } from "svelte";
      let topDiv;
      let visible = false;
      let prevOnTop;
      let closeCallback;
      export let id = "";
      function keyPress(ev) {
        //only respond if the current modal is the top one
        if (ev.key == "Escape" && onTop == topDiv) close(); //ESC
      }
      /**  API **/
      function open(callback) {
        closeCallback = callback;
        if (visible) return;
        prevOnTop = onTop;
        onTop = topDiv;
        window.addEventListener("keydown", keyPress);
        //this prevents scrolling of the main window on larger screens
        document.body.style.overflow = "hidden";
        visible = true;
        //Move the modal in the DOM to be the last child of <BODY> so that it can be on top of everything
        document.body.appendChild(topDiv);
      }
      function close(retVal) {
        if (!visible) return;
        window.removeEventListener("keydown", keyPress);
        onTop = prevOnTop;
        if (onTop == null) document.body.style.overflow = "";
        visible = false;
        if (closeCallback) closeCallback(retVal);
      }
      //expose the API
      modals[id] = { open, close };
      onDestroy(() => {
        delete modals[id];
        window.removeEventListener("keydown", keyPress);
      });
    </script>
    <div id="topModal" class:visible bind:this={topDiv} on:click={() => close()}>
      <div id="modal" on:click|stopPropagation={() => {}}>
        <svg id="close" on:click={() => close()} viewBox="0 0 12 12">
          <circle cx="6" cy="6" r="6" />
          <line x1="3" y1="3" x2="9" y2="9" />
          <line x1="9" y1="3" x2="3" y2="9" />
        </svg>
        <div id="modal-content">
          <slot />
        </div>
      </div>
    </div>
    <style>
      #topModal {
        visibility: hidden;
        z-index: 9999;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: #4448;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      #modal {
        position: relative;
        border-radius: 6px;
        background: white;
        border: 2px solid #000;
        filter: drop-shadow(5px 5px 5px #555);
        padding: 1em;
      }
      .visible {
        visibility: visible !important;
      }
      #close {
        position: absolute;
        top: -12px;
        right: -12px;
        width: 24px;
        height: 24px;
        cursor: pointer;
        fill: #f44;
        transition: transform 0.3s;
      }
      #close:hover {
        transform: scale(2);
      }
      #close line {
        stroke: #fff;
        stroke-width: 2;
      }
      #modal-content {
        max-width: calc(100vw - 20px);
        max-height: calc(100vh - 20px);
        overflow: auto;
      }
    </style>
Enter fullscreen mode Exit fullscreen mode

At this point, if you've followed along effectively, you should have a working crypto converter using Svelte in the front end and Strapi at the backend.

Conclusion

Great job, you’ve made it to the end of this tutorial. If you followed along correctly you should have a working app same as mine by now.

By now, you should be able to create an API to convert from one crypto to another using Nomic API and Strapi. You should have a basic understanding of Svelte enough to get started working with it.

Feel free to play around with what you have, modify it to suit your needs and don’t forget to share your experience with the rest of the community.
A good idea might be creating a cryptocurrency blog that has a crypto exchanger built in using Strapi.
Here is a link to the Svelte project on Github

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .