We will show you how to call StreamCardano API using ReactJS and Promise-based HTTP client KY, parse the responses, test and debug your queries.
Firstly, create a React app with a typescript template from scratch using create-react-app(CRA) in the directory, this will scaffold a basic typescript ReactJS project for us:
npx create-react-app my-app --template typescript
Let’s setup the environment variables so our Typescript code does not hold our configuration and secrets, make sure to name your environment variables starting with REACT_APP naming convention otherwise your React application will not pick them:
REACT_APP_STREAMCARDANO_HOST=beta.streamcardano.dev
REACT_APP_STREAMCARDANO_KEY=YOUR_API_KEY_HERE
Next, change into your project directory and run your application and view it on your browser at localhost:3000
cd my-app
npm start
Then let’s install the important packages that will help us use StreamCardano API:
npm i ky @microsoft/fetch-event-source react-json-pretty
Under the src directory of your project, make another directory called config and over there let’s configure our KY configurations:
import ky, { KyResponse, Options } from "ky";
/**
*
* @param request
* @param options Options are the same as window.fetch, with some exceptions.
* @param response
* @returns the body of the response as a new Fetch API interfaced response
*/
const transformResponse = (
: Request,
request: Options,
options: KyResponse
response=> {
) return new Response(response.body);
;
}
// modifying our ky instance configuration
export const kyInstance = ky
.create({ prefixUrl: process.env.REACT_APP_STREAMCARDANO_HOST })
.extend({
/**
* here we have tweaked the response to the request
* using one of the hooks provided by ky to transform our response
* this is done over here for code reusability so that we don't have
* to do the same configuration with every request
*/
: {
hooks: [transformResponse],
afterResponse,
}; })
I suggest that you start by testing our API online, to make sure your internet connection works. Under the src directory let’s make another directory called hooks and define our custom hooks in there.
import { kyInstance } from "../config/https";
/**
* Custom hook for the endpoint.
* We have defined a custom hook here for the code reusability,
* if we need the same piece of code or data is fetched from the API
* in multiple components we can consume the same custom hook defined here
* to consume data from one source of truth.
* @returns Response
*/
const useCheckStatus = async () => {
const data: Response = await kyInstance.get("api/v1/status");
return data.json();
;
}export { useCheckStatus };
Before we start consuming our custom hook let’s create the interface for the status response from StreamCardano:
/**
* @typedef StatusCheck
* @property {Array<string>} error The list of errors
* @property {IResult} result The response from the endpoint
*/
/** @type {StatusCheck} */
export interface IStatus {
: Array<string>;
errors: IResult;
result
}
/**
* @typedef Result
* @property {IAppVersionInfo} app_version_info Status information about this backend application and attached services.
* @property {Array<IDatabaseTrigger} database_triggers Database triggers set for the application.
* @property {boolean} pgbouncer_working Whether the Postgres connection pooling daemon `pgbouncer` is online.
* @property {boolean} postgres_working Whether the Postgres database is online.
* @property {number} sync_status ($PyYmMdDThHmMs[.sss]S) How far behind the blockchain is the database?
*/
/** @type {Result} */
export interface IResult {
: IAppVersionInfo;
app_version_info: Array<IDatabaseTrigger>;
database_triggers: boolean;
pgbouncer_working: boolean;
postgres_working: number;
sync_status
}
/**
* @typedef AppVersionInfo
* @property {string} appCommit Current commit hash of the StreamCardano API backend.
* @property {string} appVersion Current version of the StreamCardano API backend.
* @property {string} envName Build Environment
*/
/** @type {AppVersionInfo} */
export interface IAppVersionInfo {
: string;
appCommit: string;
appVersion: string;
envName
}
/**
* @typedef DatabaseTrigger
* @property {string} triggerEventManipulation Target action for the trigger.
* @property {string} triggerEventTable Type of event the trigger is listening to.
* @property {string} triggerName Trigger name.
*/
export interface IDatabaseTrigger {
: string;
triggerEventManipulation: string;
triggerEventTable: string;
triggerName }
Now, Call our custom hook inside custom component StatusCheck.tsx to see the response from the StreamCardano API
import React, { useEffect, useState } from "react";
/**
* This is a lightweight and tiny react component
* that helps you to format and prettify the JSON data.
*/
import JSONPretty from "react-json-pretty";
import { useCheckStatus } from "../hooks/StatusCheck";
import { IStatus } from "../interfaces/StatusCheck.interface";
/**
* Status Check Component
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function StatusCheck(): React.ReactElement {
const [status, setStatus] = useState<IStatus>();
const checkStatus = useCheckStatus();
/**
* Class component in React contains lifecycle methods
* which helps us to jump into the different states/periods of the component
* But functional components don't provide these lifecycle methods
* but it can still be achieved using the useEffect hook
* useEffect with empty braces as second argument acts as a Component Did Mount lifecycle
*/
useEffect(() => {
.then((data) => {
checkStatussetStatus(data);
;
}), []);
}
return (
<div>
<h3>
. Does not require
Retrieve status information about the backend.
authentication<br />
<strong>GET</strong> /api/v1/status
<br />
<br />
<strong>Response:</strong>
<p>
<JSONPretty data={JSON.stringify(status)} />
</p>
</h3>
</div>
;
)
}
export default StatusCheck;
The response can be transformed as well on the client side:
{
"errors": [],
"result": {
"app_version_info": {
"versionAppCommit": "0e3714d6d9b05d435ae34850ef798da26416b4a6",
"versionAppVersion": "0.1.0.0",
"versionEnvName": "TestEnv"
},
"database_triggers": [{
"event_manipulation": "INSERT",
"event_table": "block",
"name": "blocks_changed"
},
{
"event_manipulation": "DELETE",
"event_table": "block",
"name": "blocks_changed"
},
{
"event_manipulation": "UPDATE",
"event_table": "block",
"name": "blocks_changed"
}
],
"pgbouncer_working": true,
"postgres_working": true,
"sync_status": 4883.902512
}
}
You can also use this endpoint to check your work on the latest version of the application API library.
This is the only call that does not require authorization. You may also see the API status here.
You need to consume the API key for all the other endpoints:
The Authorization
header needs to be sent with
the ${REACT_APP_STREAMCARDANO_KEY}
value.
You have a developer key with a unique id for your application. For now, you may use all developer APIs at a limited rate. To deploy in production, you will get a key with only a limited functionality but a much higher allowed query rate that permits thousands of simultaneous users.
You may now check what the last block ID recorded in the database, let’s create our custom hook for this endpoint:
import { kyInstance } from "../config/https";
/**
* Custom hook for the endpoint.
* We have defined a custom hook here for the code reusability,
* if we need the same piece of code or data is fetched from the API
* in multiple components we can consume the same custom hook defined here
* to consume data from one source of truth.
* @returns Response
*/
const useGetLastBlock = async () => {
const data: Response = await kyInstance.get("api/v1/last/block", {
: {
headers: `Bearer ${process.env.REACT_APP_STREAMCARDANO_KEY}`,
Authorization,
};
})return data.json();
;
}export { useGetLastBlock };
Followed by the interface for this endpoint:
/**
* @typedef LastBlock
* @property {Array<string>} errors The list of errors
* @property {number} result The sequential number of the last block in the blockchain.
*/
/** @type {LastBlock} */
export interface ILastBlock {
: Array<string>;
errors: number;
result }
and now for the custom component for this endpoint
import React, { useEffect, useState } from "react";
/**
* This is a lightweight and tiny react component
* that helps you to format and prettify the JSON data.
*/
import JSONPretty from "react-json-pretty";
import { useGetLastBlock } from "../hooks/LastBlock";
import { ILastBlock } from "../interfaces/LastBlock.interface";
/**
* Last Block Component
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function LastBlock(): React.ReactElement {
const lastBlock = useGetLastBlock();
const [lastBlockData, setLastBlockData] = useState<ILastBlock>();
/**
* Class component in React contains lifecycle methods
* which helps us to jump into the different states/periods of the component
* But functional components don't provide these lifecycle methods
* but it can still be achieved using the useEffect hook
* useEffect with empty braces as second argument acts as a Component Did Mount lifecycle
*/
useEffect(() => {
.then((data) => {
lastBlocksetLastBlockData(data);
;
}), []);
}return (
<div>
<h3>
of the last block.
Get the number <br />
<strong>GET</strong> /api/v1/last/Block
<br />
<br />
<strong>Response:</strong>
<p>
<JSONPretty data={JSON.stringify(lastBlockData)} />
</p>
</h3>
</div>
;
)
}
export default LastBlock;
To get better performance you may want to avoid transmitting unnecessary data.
To achieve this, we will use a custom SQL query that only gets a block number, hash, and transaction count within the block.
Before we start consuming our custom hook let’s create the interface for query response from StreamCardano:
/**
* @typedef Query
* @property {Array<string>} error The list of errors
* @property {IQueryData} result The response from the endpoint
*/
/** @type {Query} */
export interface IQuery {
: Array<string>;
errors: Array<IQueryData>;
result
}
/**
* @typedef QueryData
* @property {number} block_no
* @property {string} hash
* @property {number} tx_count
*/
/** @type {QueryData} */
export interface IQueryData {
: number;
block_no: string;
hash: number;
tx_count }
import { kyInstance } from "../config/https";
/**
* Custom hook for the endpoint.
* We have defined a custom hook here for the code reusability,
* if we need the same piece of code or data is fetched from the API
* in multiple components we can consume the same custom hook defined here
* to consume data from one source of truth.
* @returns Response
*/
const useQuery = async () => {
const data: Response = await kyInstance.post("api/v1/query", {
: {
headers: `Bearer ${process.env.REACT_APP_STREAMCARDANO_KEY}`,
Authorization"Content-Type": "text/plain;charset=utf-8",
,
}: "SQL_QUERY",
body;
})return data.json();
;
}
export { useQuery };
Let’s search for the most recent transaction from any smart contract on the Testnet and consume our query hook with a new SQL query.
Query would be
SELECT tx_id, value FROM datum ORDER BY tx_id DESC LIMIT 1
:
import React, { useEffect, useState } from "react";
/**
* This is a lightweight and tiny react component
* that helps you to format and prettify the JSON data.
*/
import JSONPretty from "react-json-pretty";
import { useQuery } from "../hooks/Query";
import { IQuery } from "../interfaces/Query.interface";
/**
* Query Component
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function Query(): React.ReactElement {
const postQuery = useQuery();
const [data, setData] = useState<IQuery>();
/**
* Class component in React contains lifecycle methods
* which helps us to jump into the different states/periods of the component
* But functional components don't provide these lifecycle methods
* but it can still be achieved using the useEffect hook
* useEffect with empty braces as second argument acts as a Component Did Mount lifecycle
*/
useEffect(() => {
.then((data) => {
postQuerysetData(data);
;
}), []);
}
return (
<div>
<h3 className="Post">
.
Run a custom database query and retrieve its results<br />
<strong>POST</strong> /api/v1/query
<br />
<br />
<strong>Response:</strong>
<p>
<JSONPretty data={JSON.stringify(data)} />
</p>
</h3>
</div>
;
)
}
export default Query;
The response can be transformed as well on the client side:
[{
"tx_id": 5294399,
"value": {
"constructor": 1,
"fields": [{
"constructor": 0,
"fields": [{
"constructor": 0,
"fields": [{
"bytes": "b2ff7b709174bfc6c65b7be977b8d7320c03f0eaa8e2f5305d1b9aad"
}]
}, {
"constructor": 0,
"fields": [{
"constructor": 0,
"fields": [{
"int": 407011
}, {
"int": 1667831254999
}]
}]
}]
}]
}
}]
In case you get any error, you may use the post to
/api/v1/debug/query
and get additional debugging
information:
Before we start consuming our custom hook let’s create the interface for debug query response from StreamCardano:
/**
* @typedef DebugQuery
* @property {Array<string>} error The list of errors
* @property {IResult} result The response from the endpoint
*/
/** @type {DebugQuery} */
export interface IDebugQuery {
: Array<string>;
error: IResult;
result
}
/**
* @typedef Result
* @property {string} compile_time Time to compile the SQL query, in seconds.
* @property {Array<string>} explain PostgreSQL explanation of the query.
* @property {string} orig_sql Original SQL query, as sent in the request.
* @property {Array<string>} params Query parameters given as input.
* @property {Array<string>} results Query results.
* @property {string} sql Original SQL query, after parsing.
*/
/** @type {Result} */
export interface IResult {
: string;
compile_time: Array<string>;
explain: string;
orig_sql: Array<string>;
params: Array<string>;
results: string;
sql }
import { kyInstance } from "../config/https";
/**
* Custom hook for endpoint.
* We have defined a custom hook here for the code reusability,
* if we need the same piece of code or data is fetched from the API
* in multiple components we can consume the same custom hook defined here
* to consume data from one source of truth.
* @returns Response
*/
const useDebugQuery = async () => {
const data: Response = await kyInstance.post("api/v1/debug/query", {
: {
headers: `Bearer ${process.env.REACT_APP_STREAMCARDANO_KEY}`,
Authorization"content-type": "text/plain;charset=utf-8",
,
}: "SQL_QUERY_TO_BE_DEBUGGED",
body;
})return data.json();
;
}export { useDebugQuery };
Consuming our custom hook inside the custom component
import React, { useEffect, useState } from "react";
/**
* This is a lightweight and tiny react component
* that helps you to format and prettify the JSON data.
*/
import JSONPretty from "react-json-pretty";
import { useDebugQuery } from "../hooks/DebugQuery";
import { IDebugQuery } from "../interfaces/Debug.interface";
/**
* DebugQuery Component
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function DebugQuery(): React.ReactElement {
const DebugQuery = useDebugQuery();
const [data, setData] = useState<IDebugQuery>();
/**
* Class component in React contains lifecycle methods
* which helps us to jump into the different states/periods of the component
* But functional components don't provide these lifecycle methods
* but it can still be achieved using the useEffect hook
* useEffect with empty braces as second argument acts as a Component Did Mount lifecycle
*/
useEffect(() => {
.then((data) => {
DebugQuerysetData(data);
;
}), []);
}
return (
<div>
<h3 className="Post">
with
Run a custom database query and retrieve its results along .
additional debug information<br />
<strong>POST</strong> /api/v1/debug/query
<br />
<br />
<strong>Response:</strong>
<p>
<JSONPretty data={JSON.stringify(data)} />
</p>
</h3>
</div>
;
)
}
export default DebugQuery;
The response can be transformed as well on the client side:
{
"CompileTime": 0.003751576,
"EXPLAIN": ["Limit (cost=3106846.97..3106846.97 rows=1 width=40)", " CTE dats", " -> Merge Join (cost=1690196.68..2675203.90 rows=17265723 width=917)", " Merge Cond: (tx_out.tx_id = datum.tx_id)", " -> Index Only Scan using idx_tx_out_tx_id on tx_out (cost=0.43..786046.51 rows=13825337 width=8)", " -> Materialize (cost=1592925.03..1598390.94 rows=1093182 width=917)", " -> Sort (cost=1592925.03..1595657.99 rows=1093182 width=917)", " Sort Key: datum.tx_id", " -> Seq Scan on datum (cost=0.00..160561.82 rows=1093182 width=917)", " -> Sort (cost=431643.08..474807.38 rows=17265723 width=40)", " Sort Key: dats.tx_id DESC", " -> CTE Scan on dats (cost=0.00..345314.46 rows=17265723 width=40)"],
"OrigSQL": "WITH dats AS (SELECT datum.tx_id, datum.value FROM datum, tx_out WHERE datum.tx_id=tx_out.tx_id) SELECT * FROM dats ORDER BY tx_id DESC LIMIT 1",
"Params": [],
"SQL": "SELECT json_agg(t) FROM (WITH dats AS (SELECT datum.tx_id, datum.value FROM (SELECT datum.* FROM datum INNER JOIN tx ON datum.tx_id = tx.id INNER JOIN (SELECT * FROM block WHERE block_no <= $1) AS block ON block.id = tx.block_id ORDER BY datum.id DESC LIMIT $2) AS datum, (SELECT tx_out.* FROM tx_out INNER JOIN tx ON tx_out.tx_id = tx.id INNER JOIN (SELECT * FROM block WHERE block_no <= $1) AS block ON block.id = tx.block_id ORDER BY tx_out.id DESC LIMIT $2) AS tx_out WHERE datum.tx_id = tx_out.tx_id) SELECT * FROM dats ORDER BY tx_id DESC LIMIT 1) AS t"
}
For streaming events we will use fetchEventSource from @microsoft/fetch-event-source npm package, to smoothly receive live events.
import React, { useEffect, useState } from "react";
/**
* This package provides a better API for making Event Source requests - also known as server-sent events - with all the features available in the Fetch API.
* You can pass in all the other parameters exposed by the default fetch API, for example:
*/
import { fetchEventSource } from "@microsoft/fetch-event-source";
/**
* This is a lightweight and tiny react component
* that helps you to format and prettify the JSON data.
*/
import JSONPretty from "react-json-pretty";
/**
* Server-Sent Events Component
* Server Sent Event Component using @microsoft/fetch-event-source npm package
* to receive events from the server and keep listening
* refreshing and getting that every 10 seconds
* accepting the event-stream type of data.
* With a query for the user to decide which type of data they want
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function SSE(): React.ReactElement {
const [sseData, setSSEData] = useState<any>();
/**
* Class component in React contains lifecycle methods
* which helps us to jump into the different states/periods of the component
* But functional components don't provide these lifecycle methods
* but it can still be achieved using the useEffect hook
* useEffect with empty braces as second argument acts as a Component Did Mount lifecycle
*/
useEffect(() => {
const fetchData = async () => {
await fetchEventSource(
`${process.env.REACT_APP_STREAMCARDANO_HOST}/api/v1/sse`,
{: "POST",
method: {
headers: `Bearer ${process.env.REACT_APP_STREAMCARDANO_KEY}`,
Authorization: "text/event-stream",
Accept"Content-Type": "text/plain;charset=utf-8",
,
}: "WITH selected_blocks AS (SELECT * FROM block WHERE block_no IS NOT NULL AND block_no <= $1 :: int4 ORDER BY block_no DESC limit $2 :: int4) SELECT b.block_no :: int4, b.block_time :: timestamp?, Floor (Sum(tx.fee / b.tx_count)) :: int4? AS avg_block_fee FROM selected_blocks AS b LEFT JOIN tx ON b.id = tx.block_no GROUP BY block_no, block_time ORDER BY block_no DESC",
bodyonopen(res: Response): any {
/**
* @name 200 Status Code means a successful connection was made with the server
* @name 400 Status Code means Bad Request and there is something wrong with the HTTP request
* @name 500 Status Code means Internal Server Error a generic error
* that indicates the server encountered an unexpected condition and can’t fulfill the request.
* @name 429 Status Code means Too many requests. The server responds with this code
* when the user has sent too many requests in the given time and has exceeded the rate limit.
*/
if (res.ok && res.status === 200) {
console.log("Connection made ", res);
else if (
} .status >= 400 &&
res.status < 500 &&
res.status !== 429
res
) {console.log("Client side error ", res);
},
}onmessage(event) {
console.log(event.data);
const parsedData = JSON.parse(event.data);
setSSEData(parsedData);
,
}onclose() {
console.log("Connection closed by the server");
,
}onerror(err) {
console.log("There was an error from server", err);
,
}
};
);
}fetchData();
, []);
}return (
<div>
<h3 className="Post">
new blocks on the chain
Listen to <br />
<strong>POST</strong> /api/v1/sse
<br />
<br />
<strong>Response:</strong>
<p>
<JSONPretty data={JSON.stringify(sseData)} />
</p>
</h3>
</div>
;
)
}
export default SSE;
And you can keep receiving live and any new events every 10 seconds :
{
"data": [{
"block_no": 3930459,
"hash": "\\x8fd95640cfad377839795be890a69c58be66b5bba3831104aa499d2e010c9f6b",
"tx_count": 1
}],
"event": "new_block"
} {
"data": [{
"block_no": 3930460,
"hash": "\\xcc2d0bf9457a493404dac2444eb28baf80cac0fc611d8d395153db1abacac05a",
"tx_count": 0
}],
"event": "new_block"
}
Now to consume all our custom components in App.tsx
import React from "react";
import "./App.css";
import DebugQuery from "./components/DebugQuery";
import LastBlock from "./components/LastBlock";
import Query from "./components/Query";
import SSE from "./components/SSE";
import StatusCheck from "./components/StatusCheck";
/**
* Main App Component
* @type {React.FC<Props>}
* @returns {React.ReactElement}
*/
function App(): React.ReactElement {
return (
<div className="App">
<StatusCheck />
<Query />
<DebugQuery />
<LastBlock />
<SSE />
</div>
;
)
}
export default App;
To run this code in your local environment in the dev environment, clone the application and then:
cd my_app
npm install
npm start
and then view the application at localhost:3000.. Add your API key in .env to study responses.
You can also build this application with the following command. Then drag and drop the build folder to AWS S3 to deploy your web application.
npm run build
🚀 Calling Stream Cardano from Haskell
🚀 Streaming Events of StreamCardano API Primer in Haskell
📦 Fetch Server Sent Events by Microsoft
📦 Https client to make server requests
Entangled helps you write Literate Programs in Markdown. You put all your code inside Markdown code blocks. Entangled automatically extracts the code and writes it to more traditional source files. You can then edit these generated files, and the changes are fed back to the Markdown.
FYI, This README just tangled you. 😁