# READ Stream Data from a UI (Next.js Example)

In this guide, you’ll learn how to read data published to Somnia Data Streams directly from a Next.js frontend, the same way you’d use readContract with Viem.

We’ll build a simple HelloWorld schema and use it to demonstrate all the READ methods in the Somnia Data Streams SDK, from fetching the latest message to retrieving complete datasets or schema metadata.

***

## Prerequisites

Before we begin, make sure you have:

```bash
npm i @somnia-chain/streams viem
```

Also ensure:

* Node.js 20+
* A Somnia Testnet wallet with STT test tokens
* `.env` file containing your wallet credentials:
* A working Next.js app (npx create-next-app somnia-streams-read)
* Access to a publisher address and schema ID (or one you’ve created earlier)

***

## Set up the SDK and Client

We’ll initialize the SDK using Viem’s createPublicClient to communicate with Somnia’s blockchain.

```typescript
// lib/store.ts
import { SDK } from '@somnia-chain/streams'
import { createPublicClient, http } from 'viem'
import { somniaTestnet } from 'viem/chains'

const publicClient = createPublicClient({
  chain: somniaTestnet,
  transport: http(),
})

export const sdk = new SDK(publicClient)
```

This sets up the data-reading connection between your frontend and the Somnia testnet.

Think of it as the Streams version of [readContract()](https://viem.sh/docs/contract/readContract#readcontract); it lets you pull structured data (not just variables) directly from the blockchain.

***

## Define Schema and Publisher

A schema describes the structure of data stored in Streams, just like how a smart contract defines the structure of state variables.

```typescript
// lib/schema.ts
export const helloWorldSchema = 'uint64 timestamp, string message'
export const schemaId = '0xabc123...'   // Example Schema ID
export const publisher = '0xF9D3...E5aC' // Example Publisher Address
```

If you don’t have the schema ID handy, you can generate it from its definition:

```typescript
const computedId = await sdk.streams.computeSchemaId(helloWorldSchema)
console.log('Computed Schema ID:', computedId)
```

This ensures that you’re referencing the same schema ID under which the data was published.

***

## Fetch Latest “Hello World” Message

This is the most common use case: getting the most recent data point. For example, displaying the latest sensor reading or chat message.

```typescript
// lib/read.ts
import { sdk } from './store'
import { schemaId, publisher } from './schema'

export async function getLatestMessage() {
  const latest = await sdk.streams.getLastPublishedDataForSchema(schemaId, publisher)
  console.log('Latest data:', latest)
  return latest
}
```

This method retrieves the newest record from that schema-publisher combination.

It’s useful when:

* You’re showing a live dashboard
* You need real-time data polling
* You want to auto-refresh a view (e.g., “Last Updated at…”)

***

## Fetch by Key (e.g., message ID)

Each record can have a unique key, such as a message ID, sensor UUID, or user reference. When you know that key, you can fetch the exact record.

```typescript
export async function getMessageById(messageKey: `0x${string}`) {
  const msg = await sdk.streams.getByKey(schemaId, publisher, messageKey)
  console.log('Message by key:', msg)
  return msg
}
```

When to use:

* Fetching a message by its ID (e.g., “message #45a1”)
* Retrieving a transaction or sensor entry when you know its hash
* Building a detail view (e.g., /message/\[id] route in Next.js)

Think of it like calling readContract for one item by ID.

***

## Fetch by Index (Sequential Logs)

In sequential datasets such as logs, chat history, and telemetry, each record is indexed numerically.\
You can fetch a specific record by its position:

```typescript
export async function getMessageAtIndex(index: bigint) {
  const record = await sdk.streams.getAtIndex(schemaId, publisher, index)
  console.log(`Record at index ${index}:`, record)
  return record
}
```

When to use:

* When looping through entries in order (0, 1, 2, ...)
* To replay logs sequentially
* To test pagination logic

Example: getAtIndex(schemaId, publisher, 0n) retrieves the very first message.

***

## Fetch a Range of Records (Paginated View)

You can fetch multiple entries at once using index ranges.\
This is perfect for pagination or time-series queries.

```typescript
export async function getMessagesInRange(start: bigint, end: bigint) {
  const records = await sdk.streams.getBetweenRange(schemaId, publisher, start, end)
  console.log('Records in range:', records)
  return records
}
```

Example Use Cases:

* Displaying the last 10 chat messages: getBetweenRange(schemaId, publisher, 0n, 10n)
* Loading older telemetry data
* Implementing infinite scroll

{% hint style="info" %}
Tip: Treat start and end like array indices (inclusive start, exclusive end).\
`start` is inclusive and `end` is exclusive.
{% endhint %}

***

## Fetch All Publisher Data for a Schema

If you want to retrieve all content a publisher has ever posted to a given schema, use this.

```typescript
export async function getAllPublisherData() {
  const allData = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisher)
  console.log('All publisher data:', allData)
  return allData
}
```

When to use:

* Generating analytics or trend charts
* Migrating or syncing full datasets
* Debugging data integrity or history<br>

You can think of this as:

“Give me the entire dataset under this schema from this publisher.”

It’s the Streams equivalent of querying all events from a contract.

{% hint style="info" %}
This should be used for small data sets. For larger, paginated reading, `getBetweenRange` is recommended not to overwhelm the node returning the data.
{% endhint %}

***

## Count Total Entries

Sometimes, you just want to know how many entries exist.

```typescript
export async function getTotalEntries() {
  const total = await sdk.streams.totalPublisherDataForSchema(schemaId, publisher)
  console.log(`Total entries: ${total}`)
  return Number(total)
}
```

When to use:

* To know the total record count for pagination
* To display dataset stats (“42 entries recorded”)
* To monitor the growth of a stream

This helps determine boundaries for getBetweenRange() or detect when new data arrives.

***

## Inspect Schema Metadata

Schemas define structure, and sometimes you’ll want to validate or inspect them before reading data. First, check that a schema exists when publishing a new schema:

```typescript
  const ignoreAlreadyRegistered = true
  try {
    const txHash = await sdk.streams.registerDataSchemas(
      [
        {
          schemaName: 'hello_world',
          schema: helloSchema,
          parentSchemaId: zeroBytes32
        },
      ],
      ignoreAlreadyRegistered
    )

    if (txHash) {
      await waitForTransactionReceipt(publicClient, { hash: txHash })
      console.log(`Schema registered or confirmed, Tx: ${txHash}`)
    } else {
      console.log('Schema already registered — no action required.')
    }
  } catch (err) {
    // fallback: if the SDK doesn’t support the flag yet
    if (String(err).includes('SchemaAlreadyRegistered')) {
      console.log('Schema already registered. Continuing...')
    } else {
      throw err
    }
  }
```

This is critical to ensure your app doesn’t attempt to query a non-existent or unregistered schema — useful for user-facing dashboards.

***

## Retrieve Full Schema Information

```typescript
const schemaInfo = await sdk.streams.getSchemaFromSchemaId(schemaId)
console.log('Schema Info:', schemaInfo)
```

This method retrieves both the base schema and its extended structure, if any.\
It automatically resolves inherited schemas, so you get the full picture of what fields exist.

Example output:

```json
{
  baseSchema: 'uint64 timestamp, string message',
  finalSchema: 'uint64 timestamp, string message',
  schemaId: '0xabc123...'
}
```

This is important when you’re visualizing or decoding raw stream data, you can use the schema structure to parse fields correctly (timestamp, string, address, etc.).

***

## Example Next.js App

Now let’s render our fetched data in the UI.

### Project Setup

```bash
npx create-next-app somnia-streams-reader --typescript
cd somnia-streams-reader
npm install @somnia-chain/streams viem
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

***

### Folder Structure

```
somnia-streams-reader/
├── app/
│   ├── api/
│   │   └── latest/route.ts
│   ├── page.tsx
│   ├── layout.tsx
│   └── globals.css
├── components/
│   ├── StreamViewer.tsx
│   └── SchemaInfo.tsx
├── lib/
│   ├── store.ts
│   ├── schema.ts
│   └── read.ts
├── tailwind.config.js
└── package.json
```

***

### lib/store.ts

Sets up the Somnia SDK and connects to the testnet.

```ts
import { SDK } from "@somnia-chain/streams"
import { createPublicClient, http } from "viem"
import { somniaTestnet } from "viem/chains"

const publicClient = createPublicClient({
  chain: somniaTestnet,
  transport: http(),
})

export const sdk = new SDK(publicClient)
```

***

### lib/schema.ts

Defines your schema and publisher.

```ts
export const helloWorldSchema = "uint64 timestamp, string message"
export const schemaId = "0xabc123..." // replace with actual schemaId
export const publisher = "0xF9D3...E5aC" // replace with actual publisher
```

If you don’t know your schema ID yet, you can compute it later using:

```ts
const computed = await sdk.streams.computeSchemaId(helloWorldSchema)
console.log("Schema ID:", computed)
```

***

### lib/read.ts

Implements read helpers for your API and UI.

```ts
import { sdk } from "./store"
import { schemaId, publisher } from "./schema"

export async function getLatestMessage() {
  return await sdk.streams.getLastPublishedDataForSchema(schemaId, publisher)
}

export async function getMessagesInRange(start: bigint, end: bigint) {
  return await sdk.streams.getBetweenRange(schemaId, publisher, start, end)
}

export async function getSchemaInfo() {
  return await sdk.streams.getSchemaFromSchemaId(schemaId)
}
```

***

### app/api/latest/route.ts

A serverless route to fetch the latest message (you can add more routes for range or schema info).

```ts
import { NextResponse } from "next/server"
import { getLatestMessage } from "@/lib/read"

export async function GET() {
  const data = await getLatestMessage()
  return NextResponse.json({ data })
}
```

***

### components/StreamViewer.tsx

A live component with interactive buttons for fetching data.

<details>

<summary>StreamViewer.tsx</summary>

```tsx
"use client"
import { useState } from "react"

export default function StreamViewer() {
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(false)

  const fetchLatest = async () => {
    setLoading(true)
    try {
      const res = await fetch("/api/latest")
      const { data } = await res.json()
      setData(data)
    } catch (err) {
      console.error(err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="bg-white shadow-md p-6 rounded-2xl border">
      <h2 className="text-xl font-semibold mb-4">HelloWorld Stream Reader</h2>

      <button
        onClick={fetchLatest}
        className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
        disabled={loading}
      >
        {loading ? "Loading..." : "Fetch Latest Message"}
      </button>

      {data && (
        <pre className="bg-gray-900 text-green-300 p-4 mt-4 rounded overflow-x-auto text-sm">
          {JSON.stringify(data, null, 2)}
        </pre>
      )}
    </div>
  )
}
```

</details>

***

### components/SchemaInfo.tsx

Displays the schema metadata.

```tsx
"use client"

import { useState } from "react"

export default function SchemaInfo() {
  const [info, setInfo] = useState<any>(null)

  const fetchInfo = async () => {
    const res = await fetch("/api/latest") // just for demo; replace with /api/schema if separate route
    const { data } = await res.json()
    setInfo(data)
  }

  return (
    <div className="bg-gray-50 p-6 rounded-xl shadow">
      <h2 className="font-semibold text-lg mb-3">Schema Information</h2>
      <button
        onClick={fetchInfo}
        className="bg-gray-800 text-white px-3 py-2 rounded-md"
      >
        Load Schema Info
      </button>
      {info && (
        <pre className="bg-black text-green-300 mt-3 p-3 rounded">
          {JSON.stringify(info, null, 2)}
        </pre>
      )}
    </div>
  )
}
```

***

### app/page.tsx

Main dashboard combining both components.

```tsx
import StreamViewer from "@/components/StreamViewer"
import SchemaInfo from "@/components/SchemaInfo"

export default function Home() {
  return (
    <main className="p-10 min-h-screen bg-gray-100">
      <h1 className="text-3xl font-bold mb-8">🛰️ Somnia Data Streams Reader</h1>

      <div className="grid gap-6 md:grid-cols-2">
        <StreamViewer />
        <SchemaInfo />
      </div>
    </main>
  )
}
```

***

### app/layout.tsx

Wraps the layout globally.

```tsx
import "./globals.css"

export const metadata = {
  title: "Somnia Streams Reader",
  description: "Read on-chain data from Somnia Data Streams",
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className="antialiased">{children}</body>
    </html>
  )
}
```

***

### Run the App

```bash
npm run dev
```

Visit <http://localhost:3000> to open your dashboard.\
You’ll see a **“Fetch Latest Message”** button that retrieves data via `/api/latest` and a **Schema Info** section (ready to expand)

***

## Summary Table

| Method                        | Purpose                          | Example                                            |
| ----------------------------- | -------------------------------- | -------------------------------------------------- |
| getByKey                      | Fetch a record by unique ID      | getByKey(schemaId, publisher, dataId)              |
| getAtIndex                    | Fetch record at position         | getAtIndex(schemaId, publisher, 0n)                |
| getBetweenRange               | Retrieve records in range        | getBetweenRange(schemaId, publisher, 0n, 10n)      |
| getAllPublisherDataForSchema  | Fetch all data by publisher      | getAllPublisherDataForSchema(schemaRef, publisher) |
| getLastPublishedDataForSchema | Latest record only               | getLastPublishedDataForSchema(schemaId, publisher) |
| totalPublisherDataForSchema   | Count of entries                 | totalPublisherDataForSchema(schemaId, publisher)   |
| isDataSchemaRegistered        | Check if schema exists           | isDataSchemaRegistered(schemaId)                   |
| schemaIdToId / idToSchemaId   | Convert between Hex and readable | Useful for UI & schema mapping                     |
| getSchemaFromSchemaId         | Inspect full schema definition   | Retrieves base + extended schema                   |

<br>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
