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:
npmi@somnia-chain/streamsviem
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.
This sets up the data-reading connection between your frontend and the Somnia testnet.
Think of it as the Streams version of 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.
If you don’t have the schema ID handy, you can generate it from its definition:
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.
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.
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:
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.
Example Use Cases:
Displaying the last 10 chat messages: getBetweenRange(schemaId, publisher, 0n, 10n)
Loading older telemetry data
Implementing infinite scroll
Tip: Treat start and end like array indices (inclusive start, exclusive end).
start is inclusive and end is exclusive.
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.
When to use:
Generating analytics or trend charts
Migrating or syncing full datasets
Debugging data integrity or history
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.
This should be used for small data sets. For larger, paginated reading, getBetweenRange is recommended not to overwhelm the node returning the data.
Count Total Entries
Sometimes, you just want to know how many entries exist.
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:
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
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:
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
Folder Structure
lib/store.ts
Sets up the Somnia SDK and connects to the testnet.
lib/schema.ts
Defines your schema and publisher.
If you don’t know your schema ID yet, you can compute it later using:
lib/read.ts
Implements read helpers for your API and UI.
app/api/latest/route.ts
A serverless route to fetch the latest message (you can add more routes for range or schema info).
components/StreamViewer.tsx
A live component with interactive buttons for fetching data.
StreamViewer.tsx
components/SchemaInfo.tsx
Displays the schema metadata.
app/page.tsx
Main dashboard combining both components.
app/layout.tsx
Wraps the layout globally.
Run the App
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)
export async function getMessageById(messageKey: `0x${string}`) {
const msg = await sdk.streams.getByKey(schemaId, publisher, messageKey)
console.log('Message by key:', msg)
return msg
}
export async function getMessageAtIndex(index: bigint) {
const record = await sdk.streams.getAtIndex(schemaId, publisher, index)
console.log(`Record at index ${index}:`, record)
return record
}
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
}