The Crypto world is trending now, and I want to show you how to build a Crypto Alert App with Strapi.
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.
Before you can jump into this content, you need to have a basic understanding of the following.
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.
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.
1 npx create-strapi-app strapi-api --quickstart
2 # OR
3 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.
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.
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.
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.
1npx 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.
1 cd nextjs-crypto-stats-app
2
3 npm run dev
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.
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:
1import React, { useState } from "react";
2import Modal from "./Add";
3export async function getStaticProps() {
4 const allPostsData = getSortedPostsData();
5 return {
6 props: {
7 allPostsData,
8 },
9 };
10}
11export default function Crypto({ crypto }) {
12 const [showModal, setShowModal] = useState(false);
13 return (
14 <div className="card" onClick={() => setShowModal(true)}>
15 <h3>{crypto.id} →</h3>
16 <p>${crypto.price}</p>
17 {showModal ? (
18 <Modal
19 onClose={() => setShowModal(false)}
20 show={showModal}
21 crypto={crypto}
22 ></Modal>
23 ) : null}
24 <div id="modal-root"></div>
25 <style jsx>{`
26 .card {
27 margin: 1rem;
28 flex-basis: 10%;
29 padding: 1.5rem;
30 text-align: left;
31 color: inherit;
32 text-decoration: none;
33 border: 1px solid #eaeaea;
34 border-radius: 10px;
35 transition: color 0.15s ease, border-color 0.15s ease;
36 }
37 .card:hover,
38 .card:focus,
39 .card:active {
40 color: #0070f3;
41 border-color: #0070f3;
42 }
43 .card h3 {
44 margin: 0 0 1rem 0;
45 font-size: 1.5rem;
46 }
47 .card p {
48 margin: 0;
49 font-size: 1.25rem;
50 line-height: 1.5;
51 }
52 div.StyledModalHeader {
53 display: flex;
54 justify-content: flex-end;
55 font-size: 25px;
56 }
57 input[type="text"],
58 select,
59 textarea {
60 width: 100%;
61 padding: 12px;
62 border: 1px solid #ccc;
63 border-radius: 4px;
64 resize: vertical;
65 }
66 button {
67 background-color: #04aa6d;
68 color: white;
69 padding: 12px 20px;
70 border: none;
71 border-radius: 4px;
72 cursor: pointer;
73 float: right;
74 }
75 button {
76 width: 100%;
77 padding: 12px;
78 border: 1px solid #ccc;
79 border-radius: 4px;
80 resize: vertical;
81 }
82 div.StyledModal {
83 background: white;
84 width: 300px;
85 height: 400px;
86 border-radius: 15px;
87 padding: 15px;
88 }
89 div.StyledModalOverlay {
90 position: absolute;
91 top: 0;
92 left: 0;
93 width: 100%;
94 height: 100%;
95 display: flex;
96 justify-content: center;
97 align-items: center;
98 background-color: rgba(0, 0, 0, 0.5);
99 }
100 `}</style>
101 </div>
102 );
103}
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:
1 npm i react-modal
1 import React, { useEffect, useState } from "react";
2 import ReactDOM from "react-dom";
3 import Modal from "react-modal";
4 import { storeAlertPrice } from "../lib/Nomics";
5 const customStyles = {
6 content: {
7 top: "50%",
8 left: "50%",
9 right: "auto",
10 bottom: "auto",
11 marginRight: "-50%",
12 transform: "translate(-50%, -50%)",
13 },
14 };
15 Modal.setAppElement("#modal-root");
16
17 function Add({ show, crypto }) {
18 let subtitle;
19 const [modalIsOpen, setIsOpen] = useState(show);
20 const [price, setPrice] = React.useState(0);
21 const [isBrowser, setIsBrowser] = useState(false);
22 useEffect(() => {
23 setIsBrowser(true);
24 }, []);
25 function afterOpenModal() {
26 subtitle.style.color = "#f00";
27 }
28 function closeModal() {
29 setIsOpen(false);
30 }
31 const modalContent = modalIsOpen ? (
32 <div>
33 <Modal
34 isOpen={modalIsOpen}
35 onAfterOpen={afterOpenModal}
36 onRequestClose={closeModal}
37 style={customStyles}
38 contentLabel="Modal"
39 >
40 <button onClick={closeModal}>close</button>
41 <h2 ref={(_subtitle) => (subtitle = _subtitle)}>Enter your price</h2>
42 <form
43 onSubmit={async (e) => {
44 e.preventDefault();
45 console.log(price);
46 await storeAlertPrice(crypto, price);
47 }}
48 >
49 <input
50 name="price"
51 value={price}
52 onChange={(e) => setPrice(e.target.value)}
53 type="text"
54 />
55 <button type="submit">Set Price</button>
56 </form>
57 </Modal>
58 </div>
59 ) : null;
60 if (isBrowser) {
61 return ReactDOM.createPortal(
62 modalContent,
63 document.getElementById("modal-root")
64 );
65 }
66 return null;
67 }
68 export default Add;
69
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.
1 import Crypto from "../components/Crypto";
2 export default function Cryptos({ cryptos }) {
3 return (
4 <div className="grid">
5 {cryptos.map((crypto) => (
6 <Crypto crypto={crypto} key={crypto.id} />
7 ))}
8 <style jsx>{`
9 .grid {
10 display: flex;
11 align-items: center;
12 justify-content: center;
13 flex-wrap: wrap;
14 max-width: 1000px;
15 margin-top: 1rem;
16 }
17 @media (max-width: 600px) {
18 .grid {
19 width: 100%;
20 flex-direction: column;
21 }
22 }
23 `}</style>
24 </div>
25 );
26 }
27
Lastly, open your index.js
file in pages/index.js
folder and replace it with the following code.
1 import Head from "next/head";
2 import { useEffect } from "react";
3 import Cryptos from "../components/CryptoList";
4 import { checkAlertPrice, getCryptoData } from "../lib/Nomics";
5 export async function getStaticProps() {
6 const cryptos = await getCryptoData();
7 return {
8 props: {
9 cryptos,
10 },
11 };
12 }
13 export default function Home({ cryptos }) {
14 useEffect(() => {
15 window.setInterval(async function () {
16 const alertArray = await checkAlertPrice();
17 if (alertArray.length) alert(alertArray.map((item) => item));
18 }, 60000);
19 });
20 return (
21 <div className="container">
22 <Head>
23 <title>Crypto Alerta</title>
24 <link rel="icon" href="/favicon.ico" />
25 </Head>
26 <main>
27 <h1 className="title">
28 Welcome to <a href="https://nextjs.org">Crypto Alerta!</a>
29 </h1>
30 <p className="description">
31 Get started by clicking on each crypto currency, and adding the amount
32 you want to be notified
33 </p>
34 <Cryptos cryptos={cryptos} />
35 </main>
36 <footer>
37 <div>Crypto Alerta</div>
38 </footer>
39 <style jsx>{`
40 .container {
41 min-height: 100vh;
42 padding: 0 0.5rem;
43 display: flex;
44 flex-direction: column;
45 justify-content: center;
46 align-items: center;
47 }
48 main {
49 padding: 5rem 0;
50 flex: 1;
51 display: flex;
52 flex-direction: column;
53 justify-content: center;
54 align-items: center;
55 }
56 footer {
57 width: 100%;
58 height: 100px;
59 border-top: 1px solid #eaeaea;
60 display: flex;
61 justify-content: center;
62 align-items: center;
63 }
64 footer img {
65 margin-left: 0.5rem;
66 }
67 footer a {
68 display: flex;
69 justify-content: center;
70 align-items: center;
71 }
72 a {
73 color: inherit;
74 text-decoration: none;
75 }
76 .title a {
77 color: #0070f3;
78 text-decoration: none;
79 }
80 .title a:hover,
81 .title a:focus,
82 .title a:active {
83 text-decoration: underline;
84 }
85 .title {
86 margin: 0;
87 line-height: 1.15;
88 font-size: 4rem;
89 }
90 .title,
91 .description {
92 text-align: center;
93 }
94 .description {
95 line-height: 1.5;
96 font-size: 1.5rem;
97 }
98 code {
99 background: #fafafa;
100 border-radius: 5px;
101 padding: 0.75rem;
102 font-size: 1.1rem;
103 font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
104 DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
105 }
106 .logo {
107 height: 1em;
108 }
109 `}</style>
110 <style jsx global>{`
111 html,
112 body {
113 padding: 0;
114 margin: 0;
115 font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
116 Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
117 sans-serif;
118 }
119 * {
120 box-sizing: border-box;
121 }
122 `}</style>
123 </div>
124 );
125 }
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.
1nom i axios
Create a new folder and file called lib/Nomics.js
in the root directory and paste in the following scripts.
1 import axios from "axios";
2
3 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`;
4 export async function getCryptoData() {
5 const res = await axios.get(endpoint);
6 const cryptos = res.data;
7 await storeOrUpdate(cryptos);
8 return cryptos;
9 }
10
11 async function storeOrUpdate(cryptos) {
12 for (const key in cryptos) {
13 if (Object.hasOwnProperty.call(cryptos, key)) {
14 const newCrypto = cryptos[key];
15 const crypto = await get(newCrypto.id);
16 if (crypto) {
17 // Update
18 await newUpdate(crypto.id, newCrypto);
19 } else {
20 //Store
21 await store(newCrypto);
22 }
23 }
24 }
25 }
26
27 async function store(data) {
28 const newData = {
29 price: data.price,
30 name: data.id,
31 };
32 const res = await axios.post("http://localhost:1337/cryptos", newData);
33 return res.data;
34 }
35
36 async function newUpdate(id, data) {
37 const newData = {
38 price: data.price,
39 name: data.id,
40 };
41 await update(id, newData);
42 }
43
44 async function updateAlertPrice(id, price) {
45 const newData = {
46 alert_price: price,
47 };
48 const crypto = await get(id);
49 await update(crypto.id, newData);
50 }
51
52 async function update(id, data) {
53 const res = await axios.put(`http://localhost:1337/cryptos/${id}`, data);
54 return res.data;
55 }
56
57 async function get(name) {
58 const res = await axios.get(`http://localhost:1337/cryptos/names/${name}`);
59 if (res.data.success) {
60 return res.data.crypto;
61 }
62 return null;
63 }
64
65 export async function storeAlertPrice(crypto, alertPrice) {
66 // Store to local storage
67 localStorage.setItem(crypto.id, alertPrice);
68 //Upate to Strapi
69 await updateAlertPrice(crypto.id, alertPrice);
70 return;
71 }
72
73 async function isSamePrice(crypto) {
74 // Check localStorage prices
75 let alertPrice = localStorage.getItem(crypto.id);
76 if (parseFloat(alertPrice) >= parseFloat(crypto.price)) {
77 return true;
78 }
79 // Check Strapi prices
80 const strCrypto = await get(crypto.id);
81 if (parseFloat(strCrypto.alert_price) >= parseFloat(crypto.price)) {
82 return true;
83 }
84 return false;
85 }
86
87 export async function checkAlertPrice() {
88 //Load new Crypto prices
89 const cryptos = await getCryptoData();
90 const alertArr = [];
91 for (const key in cryptos) {
92 if (Object.hasOwnProperty.call(cryptos, key)) {
93 const crypto = cryptos[key];
94 // Check Prices
95 if (await isSamePrice(crypto)) {
96 alertArr.push(
97 `${crypto.id} has reached the ${crypto.price} amount you set`
98 );
99 }
100 }
101 }
102 return alertArr;
103 }
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 a single crypto with the crypto name.
1 //....
2
3 {
4 "method": "GET",
5 "path": "/cryptos/names/:name",
6 "handler": "cryptos.findOneByName",
7 "config": {
8 "policies": []
9 }
10 },
11
12 //....
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.
1 "use strict";
2 const { sanitizeEntity } = require("strapi-utils");
3 /**
4 * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
5 * to customize this controller
6 */
7 module.exports = {
8 async findOneByName(ctx) {
9 const { name } = ctx.params;
10 const entity = await strapi.query("cryptos").findOne({ name: name });
11 if (entity)
12 return ctx.send({
13 message: "Crypto found",
14 success: true,
15 crypto: sanitizeEntity(entity, { model: strapi.models.cryptos }),
16 });
17 return ctx.send({
18 message: "Crypto not found",
19 success: false,
20 });
21 },
22 };
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.
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.
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.
Solomon Eseme a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Mastering Backend.