How to Build a Crypto Alert App with Strapi and Next.js

Solomon Eseme - Aug 11 '21 - - Dev Community

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.
  5. Download and install Node.js

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
Enter fullscreen mode Exit fullscreen mode

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 collections

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

For instance, we will create a Collection Type called crypto that will have fields 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.

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 cryptocurrency. We will add Bitcoin as our test crypto currency because we know the current price of BTC. You can add any cryptocurrency 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 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.

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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](http://twitter.com/media) (max-width: 600px) {
              .grid {
                width: 100%;
                flex-direction: column;
              }
            }
          `}</style>
        </div>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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](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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

Seed Strapi Backend

Next, we will retrieve our live cryptocurrency prices from Nomics and store them in our Strapi Backend using the Strapi API.

Install Axios for API calls.

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`](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%60);
    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](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}`](http://localhost:1337/cryptos/%24%7Bid%7D%60), data);
      return res.data;
    }

    async function get(name) {
      const res = await axios.get(`[http://localhost:1337/cryptos/names/${name}`](http://localhost:1337/cryptos/names/%24%7Bname%7D%60));
      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;
    }
Enter fullscreen mode Exit fullscreen mode

Remember to replace the YOUR_API_KEY with your real API key gotten from the Nomics account 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 single crypto with the crypto name.

//....

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

    //....
Enter fullscreen mode Exit fullscreen mode

And also open the file cryptos.js at api/crypto/controllers/cryptos.js and add the following code. The code below implements the logic of finding 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,
        });
      },
    };
Enter fullscreen mode Exit fullscreen mode

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. These 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.

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.

Conclusion

This article 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, both the Next.js Frontend and Strapi Backend.

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

Originally published at https://strapi.io.


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