06 / 08
Create a news feed dapp
This lesson builds something more practical: a decentralized news feed where users publish posts and follow one another. You start with the core of the feed, which stores individual posts and links each one to a user.
If you come from Solidity, the following code will look familiar:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract NewsFeedClone { struct Post { string text; uint256 timestamp; address user; } struct User { string username; } event PostCreated(address indexed user, string text, uint256 timestamp); event UserCreated(address indexed user, string username); function createPost(address user, string memory _text) public { emit PostCreated(user, _text, block.timestamp); } function registerUser(string memory username) public { emit UserCreated(msg.sender, username); }}Here is the equivalent in Rell. The differences are covered afterwards:
module;entity post { content: text; timestamp: timestamp = op_context.last_block_time; author: user;}entity user { key id: byte_array; mutable name;}operation register_user(user_id: byte_array, username: text) { create user ( id = user_id, name = username );}operation create_post(author_id: byte_array, content: text) { create post ( user @ { author_id }, content = content );}The two versions are close, but they differ in important ways. The Solidity version emits an event rather than keeping the data in the contract. On EVM chains you have to account for gas fees, so storing data and logic in the contract does not scale. With Chromia and Rell, you keep all data on-chain and don't need any storage outside your dapp. Posts live in a relational database that you query directly from the dapp, and users don't pay gas fees per transaction (the dapp's hosting is paid for instead).
A closer look
Rell has data models, a concept Solidity doesn't really offer. In these examples, Solidity uses the struct keyword to group data, while Rell uses entity, which defines a real data model entity. Rell also has a struct keyword, and it behaves the same way as in Solidity; see the struct documentation for details. An entity is comparable to a table in a SQL database. When you deploy this code, Chromia's distributed relational database creates the required tables and relations for you. The example also links a user to their posts. Both implementations model this link, but only in Rell does it become an actual database relation.
entity post { content: text; timestamp = op_context.last_block_time; author: user;}entity user { user_id: byte_array; name: text;}struct Post { string text; uint256 timestamp; address user;}struct User { address user; string username;}The Rell code breaks down as follows:
- The
entitykeyword starts the entity definition, much like declaring a table in a database. - Attributes act like table columns, and each holds a specific data type belonging to the entity.
- You link a post to its author by adding the attribute
author: user. The relation is then created automatically in the database that backs the state.
With the model defined, the next step is a function that creates new instances of it. First, the Solidity version:
function createPost(address user, string memory _text) public { emit PostCreated(user, _text, block.timestamp);}A transaction that calls createPost emits an event containing the post data to the event log. Events are a gas-efficient way to record information in Solidity. Because every Ethereum transaction costs gas, it makes sense to minimize expensive on-chain storage in your contract. Events write data to transaction receipts and use less gas than storing data directly on the blockchain. The downside is a trade-off between centralization and decentralization.
The Rell function looks much the same:
operation create_post(author_id: byte_array, content: text) { val author = user @ { author_id } create post ( author, content = content );}In Rell, an operation is a function whose body changes the dapp's state. A transaction holds one or more operations, and each operation's logic modifies the dapp's table state. You use the create keyword to insert a new post into the state. To link the post to its author, you first fetch the author entity and pass it as an argument to the create call. Executing the transaction inserts the post, related to its author, into the state, and the transaction is then added to the Chromia blockchain.
Fetching posts for a user
Both implementations now model users and their posts, so you can add a way to fetch the posts of a given user. The Solidity contract emits an event for each new post, so you have to query the event log rather than the contract itself.
const contract = new web3.eth.Contract(contractABI, contractAddress);contract .getPastEvents("PostCreated", { filter: { user: "0x123...", // Ethereum address of the user }, fromBlock: fetchFromBlock, toBlock: "latest", }) .then(function (events) { console.log(events); // Perform logic here });This example fetches and filters events with web3.js. In a production app, you would likely move this to a backend and expose it to the client through an API. With Chromia and Rell, you fetch the data on-chain.
query get_my_posts(user_id: byte_array) { return post @* { .author.id == user_id } ( .content );}Thanks to the relational model, you can write a strongly typed query that returns every post by a given user in just two lines, using Rell's query syntax. Here is how it works.
The first line, val user = user @ { .id == user_id }, uses user @ to fetch a single entity, and the filter { .id == user_id } selects which user. The second query uses the @* operator in post @* to return a collection of post entities; @* indicates that the query may return zero or more results. The braces in post @* { .author == user } hold the filter condition. The last part of the query names the attribute to return, here .content. The database documentation covers operators and database operations in more depth.
That completes the first part of the news feed dapp alongside its Solidity counterpart. The app can now create posts and track which user owns each one.