.. and press ENTER to ask a question on web5, how to write code and more.

Skip to main content

Build a Chat App

Welcome to Web5, the magical land where Web2 and Web3 intertwine. My name is Alice. I am a wanderer in search of an application where I can send short-text messages to my best friend, Bob.

πŸš€ Your mission

Build Dinger, a self-sovereign, modern day pager that allows users to send short text-based messages or "dings" to each other.

πŸ› οΈ Your toolkit

To build this app, you will wield the following technologies:

  • React - A JavaScript library, your brush to paint user interfaces, craft reusable components, and manage the state of your creation.
  • Next.js - A React metaframework, your compass to guide you through creating pages, managing routes, and managing server-side rendering.
  • Web5.js - A Web5 JavaScript SDK, your key to creating decentralized applications, facilitating direct communication between users, and granting them sovereignty over their data and identity.

πŸ† Your reward

By the end of this journey, you will acquire a deeper understanding of peer-to-peer communication in a Web5 ecosystem.

If you're ready for the challenge, let's get started!

note

This tutorial is most suitable for those who prefer to learn by doing.

Level 1: Explore the starter code​

Good news! You don't have to start from scratch. There is a pre-built application that contains the user interface for Dinger. Right now, the functions in the code don’t do much. They just print messages to the console and don’t have any real actions. As you progress through the tutorial, you will add logic to the functions.

πŸ“ Task​

To access the starter code, follow the instructions below or explore the project directly in CodeSandbox.

Note: If you don't have pnpm installed, you can install it by running npm install -g pnpm.

git clone https://github.com/TBD54566975/developer.tbd.website.git
cd developer.tbd.website
pnpm install
pnpm start:tutorial-dinger-starter

Play in CodeSandbox

Finished Dinger App

If you’d like to skip ahead and see the finished version of this tutorial, you can check out the running app on CodeSandbox.

Play in CodeSandbox

You can also download and run the example by executing:

Note: If you don't have pnpm installed, you can install it by running npm install -g pnpm.

git clone https://github.com/TBD54566975/developer.tbd.website.git
cd developer.tbd.website
pnpm install
pnpm start:tutorial-dinger-completed

There is also a hosted example deployed at https://dinger-chat.vercel.app/

To fully interact with the web app, please click "Open in New Tab" as demonstrated in the GIF below.

Description of the image

πŸ•΅οΈ What to Explore​

  • Navigate to the src/pages/index.js file.
  • Identify the functions that currently only print console.logs.
  • Try to understand how each function interacts.

Level 2: Connect to Web5 and create a DID​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log(`this log is in initWeb5`);

and replace it with the following code snippet:

src/pages/index.js
const { web5, did } = await Web5.connect();
setWeb5(web5);
setMyDid(did);

🧩 Breaking it down​

  • Web5.connect() returns a Web5 instance: It initializes and returns an instance of Web5, allowing you to interact with the Web5 ecosystem.
  • Web5.connect() also creates or connects to a DID: A DID (Decentralized Identifier) is a user’s unique ID card for the decentralized, digital world. It belongs only to you. Web5.connect() will either connect to an existing DID that you have or create a new one for you if you don't have one yet. A DID acts as an authenticator for Web5 apps, removing the need for login credentials.

πŸ“˜ Learn more​

Learn more about DID authentication.

Level 3: Define a protocol​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log('this log is in createProtocolDefinition');

And replace it with the following code snippet:

src/pages/index.js
const dingerProtocolDefinition = {
protocol: "https://blackgirlbytes.dev/dinger-chat-protocol",
published: true,
types: {
ding: {
schema: "https://blackgirlbytes.dev/ding",
dataFormats: ["application/json"],
},
},
structure: {
ding: {
$actions: [
{ who: "anyone", can: ["create"] },
{ who: "author", of: "ding", can: ["read"] },
{ who: "recipient", of: "ding", can: ["read"] },
],
},
},
};
return dingerProtocolDefinition;

🧩 Breaking it down​

  • dingerProtocolDefinition: This object defines the protocol, its structure, and it grants permissions outlining who can perform specific actions like reading or writing a ding.

πŸ“˜ Learn more​

Learn more about defining protocols.

Level 4: Install the defined protocol​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log(`this log is in installProtocolLocally`);

And replace it with the following code snippet:

src/pages/index.js
    return await web5.dwn.protocols.configure({
message: {
definition: protocolDefinition,
},
});

🧩 Breaking it down​

  • web5.dwn.protocols.configure(): This function installs the protocol. The protocol needs to be installed because it ensures that both the sender's and recipient's applications (or Decentralized Web Nodes - DWNs) are aligned in terms of communication standards and data formats.

Level 5: Query the protocol​

πŸ“ Task​

src/pages/index.js
console.log(`this log is in queryForProtocol`);

And replace it with the following code snippet:

src/pages/index.js
return await web5.dwn.protocols.query({
message: {
filter: {
protocol: "https://blackgirlbytes.dev/dinger-chat-protocol",
},
},
});

🧩 Breaking it down​

  • web5.dwn.protocols.query(): This function checks if the protocol already exists

πŸ“˜ Learn more​

Learn more about querying protocols.

Level 6: Bring all the protocol steps together​

πŸ“ Task​

src/pages/index.js
console.log(`this log is in configureProtocol`);

And replace it with the following code snippet:

src/pages/index.js
const protocolDefinition = await createProtocolDefinition();

const { protocols: localProtocol, status: localProtocolStatus } = await queryForProtocol(web5);

console.log({ localProtocol, localProtocolStatus });

if (localProtocolStatus.code !== 200 || localProtocol.length === 0) {

const { protocol, status } = await installProtocolLocally(web5, protocolDefinition);
console.log("Protocol installed locally", protocol, status);
const { status: configureRemoteStatus } = await protocol.send(did);
console.log("Did the protocol install on the remote DWN?", configureRemoteStatus);

} else {
console.log("Protocol already installed");
}
  • configureProtocol(): This function is responsible for configuring the protocol in both the local and remote DWNs. It first checks if the protocol is already installed locally. If not, it installs the protocol locally and then attempts to install it on the remote DWN associated with the user's DID.

πŸ“˜ Learn more​

Level 7: Write to the DWN​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log(`this log is in writeToDwn`);

And replace it with the following code snippet:

src/pages/index.js
const { record } = await web5.dwn.records.write({
data: ding,
message: {
protocol: "https://blackgirlbytes.dev/dinger-chat-protocol",
protocolPath: "ding",
schema: "https://blackgirlbytes.dev/ding",
recipient: recipientDid,
},
});
return record;

🧩 Breaking it down​

  • You are storing each ding on a Decentralized Web Node (DWN), or personal data store. This method web5.dwn.records.write() is responsible for writing data to the DWN.

πŸ“˜ Learn more​

Level 8: Send dings​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log(`this log is in sendRecord`);

And replace it with the following code snippet:

src/pages/index.js
return await record.send(recipientDid);

🧩 Breaking it down​

  • Users can have multiple DWNs, such as on their phone, laptop, or in the cloud. The method record.send() immediately sends the ding to all of the recipient's Decentralized Web Nodes (DWNs).

πŸ“˜ Learn more​

Level 9: Retrieve dings​

πŸ“ Task 1​

Locate the line:

src/pages/index.js
console.log(`this log is in fetchSentMessages`);

And replace it with the following code snippet:

src/pages/index.js
const response = await web5.dwn.records.query({
message: {
filter: {
protocol: "https://blackgirlbytes.dev/dinger-chat-protocol",
},
},
});

if (response.status.code === 200) {
const sentDings = await Promise.all(
response.records.map(async (record) => {
const data = await record.data.json();
return data;
})
);
console.log(sentDings, "I sent these dings");
return sentDings;
} else {
console.log("error", response.status);
}

πŸ“ Task 2​

Locate the line:

src/pages/index.js
console.log(`this log is in fetchReceivedMessages`);

And replace it with the following code snippet:

src/pages/index.js
const response = await web5.dwn.records.query({
from: did,
message: {
filter: {
protocol: "https://blackgirlbytes.dev/dinger-chat-protocol",
schema: "https://blackgirlbytes.dev/ding",
},
},
});

if (response.status.code === 200) {
const receivedDings = await Promise.all(
response.records.map(async (record) => {
const data = await record.data.json();
return data;
})
);
console.log(receivedDings, "I received these dings");
return receivedDings;
} else {
console.log("error", response.status);
}

πŸ“ Task 3​

Locate the line:

src/pages/index.js
console.log(`this log is in fetchDings`);

And replace it with the following code snippet:

src/pages/index.js
const sentMessages = await fetchSentMessages(web5, did);
const receivedMessages = await fetchReceivedMessages(web5, did);
const allMessages = [...(sentMessages || []), ...(receivedMessages || [])];
setAllDings(allMessages);

🧩 Breaking it down​

πŸ“˜ Learn more​

Level 10: Sync the UI with the DWN​

πŸ“ Task​

Locate the line:

src/pages/index.js
console.log(`this log is in intervalId`);

And replace it with the following code snippet:

src/pages/index.js
await fetchDings(web5, myDid);

This line is located within the following function:

src/pages/index.js
  useEffect(() => {
if (!web5 || !myDid) return;

const intervalId = setInterval(async () => {
console.log(`this log is in intervalId`);
}, 2000);

return () => clearInterval(intervalId);
}, [web5, myDid]);

🧩 Breaking it down​

  • The useEffect() and setInterval() fetch new dings every two seconds, allowing the UI to accurately display the chat.

πŸ“˜ Learn more​

Level 11: Try it out!​

πŸ“ Task​

Time to test the Dinger app by simulating a conversation between two users by using two different browser sessions.

Set Up Your Browsers:

  • Open your browser in regular mode.
  • Open a new window in incognito/private mode.

Send a Message from Incognito to Regular Mode:

  • In the incognito window, locate and press the Copy DID button to copy the DID.
  • Switch to the regular mode browser window.
  • Press the Create + button
  • Paste the copied DID into the recipient DID input box, then Confirm
  • Type a short message in the note input box.
  • Press Send to send the message.

Reply from Regular Mode to Incognito:

  • In the regular mode window, locate and press the Copy DID button to copy the DID.
  • Switch back to the incognito window.
  • Press the Create + button
  • Paste the copied DID into the recipient DID input box, then Confirm
  • Type a reply in the note input box.
  • Press Send to send your reply.

After following these steps, you should see a conversation taking place between the two browser sessions!

Details

Not receiving any messages? Sync intervals occur every two minutes. If you're eager to see immediate updates for testing purposes, you can adjust the sync settings. Update the line:

src/pages/index.js
const { web5, did } = await Web5.connect();

to:

src/pages/index.js
const { web5, did } = await Web5.connect({sync: '5s'});

This change forces a synchronization interval to happen every 5 seconds. However, be cautious: this is not recommended for production applications as it can lead to higher resource usage, battery drain, and potential rate limiting.

🧠 Knowledge check​

🫑 Mission complete​

Congratulations! By building Dinger, you've successfully empowered users like Alice to communicate peer-to-peer and maintain sovereignty over her data and identity. Now, Alice can send messages to her best friend, Bob. You can check out the finished version of the app running on CodeSandbox.

Play in CodeSandbox

Connect with us on Discord

Submit feedback: Open a GitHub issue

Edit this page: GitHub Repo

Contribute: Contributing Guide