How to Build a Crypto Alert App with Strapi and Next.js
Tags: nextjs, crypto, strapi, javascript
Published: 2021-07-16 16:18:38

The Crypto world is trending now, and I want to show you how to build a Crypto Alert App with Strapi.

# Outline
- Introduction
- What is Strapi - A Headless CMS?
- Scaffolding a Strapi project
- Build the Crypto collections
- Building the Next.js app
- Build components
- Seed the Strapi
- Allowing Public access
- Testing the app
- Conclusion.

The article will be focused on introducing you to building a real-world Crypto Currency alerting System with Next.js and Strapi as the Backend.

# Prerequisites

Before you can jump into this content, you need to have a basic understanding of the following.

1. Basic knowledge of JavaScript
2. Basic knowledge of ReactJS
3. Basic knowledge of Next.js
4. Basic understanding of Strapi - [get started here](https://strapi.io/documentation/developer-docs/latest/getting-started/introduction.html).
5. Download and install [Node.js](https://nodejs.org/en/)
# What is Strapi - A Headless CMS?

Strapi is an open-source headless CMS based on Node.js that is used to develop and manage content using Restful APIs and GraphQL.

With Strapi, we can scaffold our API faster and consume the content via APIs using any HTTP client or GraphQL enabled frontend.

# Scaffolding a Strapi project

To scaffold a new Strapi project is very simple and works precisely as installing a new frontend framework.

We are going to start by running the following commands and testing them out in our default browser.

```
    npx create-strapi-app strapi-api --quickstart
    # OR
    yarn create strapi-app strapi-api --quickstart
```

The command above will scaffold a new strapi project in the directory you specified.

Next, run `yarn build` to build your app and `yarn develop` to run the new project if it doesn't start automatically.

The last command will open a new tab with a page to register your new admin of the system. Go ahead and fill out the form and click on the submit button to create a new Admin.

# Build the Crypto collection

Next, we will create a new Collection Type that will store the details of each crypto currency.

For instance, we will create a Collection Type called `crypto` that will have feilds like `name`, `price`, `alert_price`.

To create our first Collection Type, log into the Admin dashboard and follow these steps.

Click `Collection Type Builder` on the left side of the page. Click on `create New Collection Type` still at the left side of the page and fill in `Crypto` as the display name.

Click on `Continue` to create a new `Crypto` collection. If you noticed, I have created other collections. That's the flexibility of Strapi.

We need to fill the `Crypto` collection with lots of Crypto data. You can achieve this in two ways: using the Admin UI and using Strapi generated API.

We will use the Admin UI to fill in one test cryptocurrency (on it soon). Click on `Continue`, and it will present you with another modal to select fields for your Collection Type.

![](https://paper-attachments.dropbox.com/s_949B1EB235062E3E50B3B25E987553E5E3DD6BF5079A8375FDF9A9E334519FCD_1626084869514_-c8JGnli.png)


Select `Text` and fill in `Name` at the Text field. Click on `Add another field` and select `Number` (float type) for the `price` and `alert_price` fields.

After adding all the required fields, click on `Save` to save the collection and click on the `Crypto` name on the left side.

Next, click on the `Add new crypto` button to add a new crypto currency. We will add Bitcoin as our test crypto currency because we know the current price of BTC. You can add any crypto currency of your choice and click on the Save and Publish buttons afterward.

# Building the Next.js app

Now that we have our Backend all figured out and configured, we will move on to creating and developing our frontend with Next.js to consume our backend APIs.

That's one of the benefits of using Strapi, and you don't need to learn or [master backend development](https://masteringbackend.com/posts/backend-development-the-ultimate-guide/) to get started.

To create a new Next.js project, follow these steps to get started. But before that, let's talk about Next.js.

Next.js is a React framework for production, and it gives the best developer experience with all the features you need for production. You can learn more about Next.js from the [official documentation](https://nextjs.org/docs/getting-started).

To create a new Next.js app, I prefer to use this quickstart template to speed up the process.


    npx create-next-app nextjs-crypto-stats-app --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"

The command created a new Next.js app called `nextjs-crypto-stats-app` using NPM as the build tool and the `learn-starter` as the example template.

Next, navigate to the folder and build the project with the following commands.

```
    cd nextjs-crypto-stats-app
    
    npm run dev
```

If everything goes well for you, you should be greeted with this welcome screen when you visit `localhost:3000`.


![](https://paper-attachments.dropbox.com/s_949B1EB235062E3E50B3B25E987553E5E3DD6BF5079A8375FDF9A9E334519FCD_1626091939285_nextjs.png)


Now, open the folder in any code editor of your choice, and let's start coding the project together.


## Building Components

NextJS is incredible with its component-based architecture, and we can develop our application by splitting the features into minor components.

Firstly, create a new folder in the root directory called `components` and create the following file inside.

Create a `Crypto.js` file and paste in the following code:

```
    import React, { useState } from "react";
    import Modal from "./Add";
    export async function getStaticProps() {
      const allPostsData = getSortedPostsData();
      return {
        props: {
          allPostsData,
        },
      };
    }
    export default function Crypto({ crypto }) {
      const [showModal, setShowModal] = useState(false);
      return (
        <div className="card" onClick={() => setShowModal(true)}>
          <h3>{crypto.id} &rarr;</h3>
          <p>${crypto.price}</p>
          {showModal ? (
            <Modal
              onClose={() => setShowModal(false)}
              show={showModal}
              crypto={crypto}
            ></Modal>
          ) : null}
          <div id="modal-root"></div>
          <style jsx>{`
            .card {
              margin: 1rem;
              flex-basis: 10%;
              padding: 1.5rem;
              text-align: left;
              color: inherit;
              text-decoration: none;
              border: 1px solid #eaeaea;
              border-radius: 10px;
              transition: color 0.15s ease, border-color 0.15s ease;
            }
            .card:hover,
            .card:focus,
            .card:active {
              color: #0070f3;
              border-color: #0070f3;
            }
            .card h3 {
              margin: 0 0 1rem 0;
              font-size: 1.5rem;
            }
            .card p {
              margin: 0;
              font-size: 1.25rem;
              line-height: 1.5;
            }
            div.StyledModalHeader {
              display: flex;
              justify-content: flex-end;
              font-size: 25px;
            }
            input[type="text"],
            select,
            textarea {
              width: 100%;
              padding: 12px;
              border: 1px solid #ccc;
              border-radius: 4px;
              resize: vertical;
            }
            button {
              background-color: #04aa6d;
              color: white;
              padding: 12px 20px;
              border: none;
              border-radius: 4px;
              cursor: pointer;
              float: right;
            }
            button {
              width: 100%;
              padding: 12px;
              border: 1px solid #ccc;
              border-radius: 4px;
              resize: vertical;
            }
            div.StyledModal {
              background: white;
              width: 300px;
              height: 400px;
              border-radius: 15px;
              padding: 15px;
            }
            div.StyledModalOverlay {
              position: absolute;
              top: 0;
              left: 0;
              width: 100%;
              height: 100%;
              display: flex;
              justify-content: center;
              align-items: center;
              background-color: rgba(0, 0, 0, 0.5);
            }
          `}</style>
        </div>
      );
    }
 ```   

Next, create a file in the `components` directory called `Add.js` and paste in the following code. You will also have to install react-modal:

```
    npm i react-modal
```

```
    import React, { useEffect, useState } from "react";
    import ReactDOM from "react-dom";
    import Modal from "react-modal";
    import { storeAlertPrice } from "../lib/Nomics";
    const customStyles = {
      content: {
        top: "50%",
        left: "50%",
        right: "auto",
        bottom: "auto",
        marginRight: "-50%",
        transform: "translate(-50%, -50%)",
      },
    };
    Modal.setAppElement("#modal-root");
    
    function Add({ show, crypto }) {
      let subtitle;
      const [modalIsOpen, setIsOpen] = useState(show);
      const [price, setPrice] = React.useState(0);
      const [isBrowser, setIsBrowser] = useState(false);
      useEffect(() => {
        setIsBrowser(true);
      }, []);
      function afterOpenModal() {
        subtitle.style.color = "#f00";
      }
      function closeModal() {
        setIsOpen(false);
      }
      const modalContent = modalIsOpen ? (
        <div>
          <Modal
            isOpen={modalIsOpen}
            onAfterOpen={afterOpenModal}
            onRequestClose={closeModal}
            style={customStyles}
            contentLabel="Modal"
          >
            <button onClick={closeModal}>close</button>
            <h2 ref={(_subtitle) => (subtitle = _subtitle)}>Enter your price</h2>
            <form
              onSubmit={async (e) => {
                e.preventDefault();
                console.log(price);
                await storeAlertPrice(crypto, price);
              }}
            >
              <input
                name="price"
                value={price}
                onChange={(e) => setPrice(e.target.value)}
                type="text"
              />
              <button type="submit">Set Price</button>
            </form>
          </Modal>
        </div>
      ) : null;
      if (isBrowser) {
        return ReactDOM.createPortal(
          modalContent,
          document.getElementById("modal-root")
        );
      }
      return null;
    }
    export default Add;
    
```

This code will pop up when a user clicks on any cryptocurrency to specify the price to be notified.

Next, create a file in the same directory called `CryptoList.js` and paste in the following code.

```
    import Crypto from "../components/Crypto";
    export default function Cryptos({ cryptos }) {
      return (
        <div className="grid">
          {cryptos.map((crypto) => (
            <Crypto crypto={crypto} key={crypto.id} />
          ))}
          <style jsx>{`
            .grid {
              display: flex;
              align-items: center;
              justify-content: center;
              flex-wrap: wrap;
              max-width: 1000px;
              margin-top: 1rem;
            }
            @media (max-width: 600px) {
              .grid {
                width: 100%;
                flex-direction: column;
              }
            }
          `}</style>
        </div>
      );
    }
    
```

Lastly, open your `index.js` file in `pages/index.js` folder and replace it with the following code.

```
    import Head from "next/head";
    import { useEffect } from "react";
    import Cryptos from "../components/CryptoList";
    import { checkAlertPrice, getCryptoData } from "../lib/Nomics";
    export async function getStaticProps() {
      const cryptos = await getCryptoData();
      return {
        props: {
          cryptos,
        },
      };
    }
    export default function Home({ cryptos }) {
      useEffect(() => {
        window.setInterval(async function () {
          const alertArray = await checkAlertPrice();
          if (alertArray.length) alert(alertArray.map((item) => item));
        }, 60000);
      });
      return (
        <div className="container">
          <Head>
            <title>Crypto Alerta</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <main>
            <h1 className="title">
              Welcome to <a href="https://nextjs.org">Crypto Alerta!</a>
            </h1>
            <p className="description">
              Get started by clicking on each crypto currency, and adding the amount
              you want to be notified
            </p>
            <Cryptos cryptos={cryptos} />
          </main>
          <footer>
            <div>Crypto Alerta</div>
          </footer>
          <style jsx>{`
            .container {
              min-height: 100vh;
              padding: 0 0.5rem;
              display: flex;
              flex-direction: column;
              justify-content: center;
              align-items: center;
            }
            main {
              padding: 5rem 0;
              flex: 1;
              display: flex;
              flex-direction: column;
              justify-content: center;
              align-items: center;
            }
            footer {
              width: 100%;
              height: 100px;
              border-top: 1px solid #eaeaea;
              display: flex;
              justify-content: center;
              align-items: center;
            }
            footer img {
              margin-left: 0.5rem;
            }
            footer a {
              display: flex;
              justify-content: center;
              align-items: center;
            }
            a {
              color: inherit;
              text-decoration: none;
            }
            .title a {
              color: #0070f3;
              text-decoration: none;
            }
            .title a:hover,
            .title a:focus,
            .title a:active {
              text-decoration: underline;
            }
            .title {
              margin: 0;
              line-height: 1.15;
              font-size: 4rem;
            }
            .title,
            .description {
              text-align: center;
            }
            .description {
              line-height: 1.5;
              font-size: 1.5rem;
            }
            code {
              background: #fafafa;
              border-radius: 5px;
              padding: 0.75rem;
              font-size: 1.1rem;
              font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
                DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
            }
            .logo {
              height: 1em;
            }
          `}</style>
          <style jsx global>{`
            html,
            body {
              padding: 0;
              margin: 0;
              font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
                Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
                sans-serif;
            }
            * {
              box-sizing: border-box;
            }
          `}</style>
        </div>
      );
    }
```
    
## Seed the Strapi

Next, we will retrieve our live cryptocurrency prices from [Nomics](https://nomics.com/) and store them in our Strapi Backend using the Strapi API.

Install Axios for API calls.


    nom i axios

Create a new folder and file called `lib/Nomics.js` in the root directory and paste in the following scripts.

```
    import axios from "axios";
    
    const endpoint = `https://api.nomics.com/v1/currencies/ticker?key=YOUR_API_KEY&ids=BTC,ETH,XRP,SHIB,ADA,YFI,DOGE,CKB,DOT,SUSHI.BTT,DENT,MATIC,CHZ&interval=1d,30d&convert=USD&per-page=100&page=1`;
    export async function getCryptoData() {
      const res = await axios.get(endpoint);
      const cryptos = res.data;
      await storeOrUpdate(cryptos);
      return cryptos;
    }
    
    async function storeOrUpdate(cryptos) {
      for (const key in cryptos) {
        if (Object.hasOwnProperty.call(cryptos, key)) {
          const newCrypto = cryptos[key];
          const crypto = await get(newCrypto.id);
          if (crypto) {
            // Update
            await newUpdate(crypto.id, newCrypto);
          } else {
            //Store
            await store(newCrypto);
          }
        }
      }
    }
    
    async function store(data) {
      const newData = {
        price: data.price,
        name: data.id,
      };
      const res = await axios.post("http://localhost:1337/cryptos", newData);
      return res.data;
    }
    
    async function newUpdate(id, data) {
      const newData = {
        price: data.price,
        name: data.id,
      };
      await update(id, newData);
    }
    
    async function updateAlertPrice(id, price) {
      const newData = {
        alert_price: price,
      };
      const crypto = await get(id);
      await update(crypto.id, newData);
    }
    
    async function update(id, data) {
      const res = await axios.put(`http://localhost:1337/cryptos/${id}`, data);
      return res.data;
    }
    
    async function get(name) {
      const res = await axios.get(`http://localhost:1337/cryptos/names/${name}`);
      if (res.data.success) {
        return res.data.crypto;
      }
      return null;
    }
    
    export async function storeAlertPrice(crypto, alertPrice) {
      // Store to local storage
      localStorage.setItem(crypto.id, alertPrice);
      //Upate to Strapi
      await updateAlertPrice(crypto.id, alertPrice);
      return;
    }
    
    async function isSamePrice(crypto) {
      // Check localStorage prices
      let alertPrice = localStorage.getItem(crypto.id);
      if (parseFloat(alertPrice) >= parseFloat(crypto.price)) {
        return true;
      }
      // Check Strapi prices
      const strCrypto = await get(crypto.id);
      if (parseFloat(strCrypto.alert_price) >= parseFloat(crypto.price)) {
        return true;
      }
      return false;
    }
    
    export async function checkAlertPrice() {
      //Load new Crypto prices
      const cryptos = await getCryptoData();
      const alertArr = [];
      for (const key in cryptos) {
        if (Object.hasOwnProperty.call(cryptos, key)) {
          const crypto = cryptos[key];
          // Check Prices
          if (await isSamePrice(crypto)) {
            alertArr.push(
              `${crypto.id} has reached the ${crypto.price} amount you set`
            );
          }
        }
      }
      return alertArr;
    }
```
    

Remember to replace the `YOUR_API_KEY` with your real API key gotten from [the Nomics account](https://nomics.com/docs/#section/Introduction) and specify the names of all the Cryptocurrencies you want to retrieve their prices.

Finally, update the following files to complete the project.

Open your Strapi backend directory and goto `api/cryptos/config/routes.js`, and add the following code. The code will create a new route in your Strapi backend to find a single crypto with the crypto name.

```
    //....
    
        {
          "method": "GET",
          "path": "/cryptos/names/:name",
          "handler": "cryptos.findOneByName",
          "config": {
            "policies": []
          }
        },
    
    //....
```

And also open the file `cryptos.js` at `api/crypto/controllers/cryptos.js` and add the following code. The code below impliments the logic of finding a single crypto from our Strapi collection using the route we defined above.

```
    "use strict";
    const { sanitizeEntity } = require("strapi-utils");
    /**
     * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
     * to customize this controller
     */
    module.exports = {
      async findOneByName(ctx) {
        const { name } = ctx.params;
        const entity = await strapi.query("cryptos").findOne({ name: name });
        if (entity)
          return ctx.send({
            message: "Crypto found",
            success: true,
            crypto: sanitizeEntity(entity, { model: strapi.models.cryptos }),
          });
        return ctx.send({
          message: "Crypto not found",
          success: false,
        });
      },
    };
```

# Allowing Public access

After creating the Cryptos collection successfully, it's time to allow public access to the collection because access will be denied if we try to access it with our public HTTP client.

To allow public access, follow these steps to enable permissions and roles in the Strapi Admin dashboard.

Click on the `Settings` item on the sidebar menu, then on the `Roles` item on the second sidebar menu that appears. On the right section, click on the `Public` item and scroll down.

You will see all the APIs with their handlers. Click on the `Select all` checkbox and click on the `Save` button at the top. This settings will allow public access to all the Crypto APIs in our Strapi project.

# Testing the app

If everything works correctly at this stage, it should present you with an excellent webpage like the one below.


![](https://paper-attachments.dropbox.com/s_949B1EB235062E3E50B3B25E987553E5E3DD6BF5079A8375FDF9A9E334519FCD_1626119967321_crypto_alerta.png)


Now, let's demo the project with the video below. We will choose a currency, set the price, and hopefully be notified when the price reaches the set amount.


<iframe width="560" height="315" src="https://www.youtube.com/embed/Xh1mFBRvDjI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>



# Conclusion

This article has been written by Solomon Eseme and published on Strapi. It demonstrated how to build a real-world Crypto Currency alerting system with Next.js and Strapi as the Backend.

You can download the source code from this [code repository](https://github.com/Kaperskyguru/crypto_alerta), both the Next.js Frontend and Strapi Backend.

Let me know you have any suggestions and what you will be building with the knowledge.