Event Streaming sample app
This reference application show how you can use Fauna Event Streaming to build real-time apps. You can use it as a starting point for your own app.
The complete source code can be found on GitHub.
Overview of Event Streaming
Fauna’s Event Streaming feature can manage real-time data processing in your applications. You can create and subscribe to event sources for collections, indexes, or a single document.
To create an event source, write a query defining the
documents to watch and convert it to an event source
using set.eventSource()
or set.eventsOn()
.
The following example creates an event source on a collection:
Product.all().eventSource()
In the example you are you subscribing to a collection called
Product
. When a new document is added, updated or deleted in the
Product
collection, the event source emits an event.
The following is another example of creating an event source
using the .eventsOn()
method:
Product.all().eventsOn(.price)
.eventsOn()
only creates an event when changes are made to
the defined fields. In the example, it is watching for changes
to the price
field in the Products
collection.
You can also subscribe to a single document:
let product = Product.where(.name == "cups").first()
Set.single(product).eventSource()
Set up the sample application
-
Clone the repository from GitHub:
git clone https://github.com/fauna-labs/chat-app-streaming
-
Install the dependencies:
cd chat-app-streaming npm install
-
Next configure Fauna database. Log in Fauna using the Fauna CLI:
fauna cloud-login
When prompted, enter:
-
Endpoint name:
cloud
(Press Enter)An endpoint defines the settings the CLI uses to run API requests against a Fauna account or database. See Endpoints.
-
Email address: The email address for your Fauna account.
-
Password: The password for your Fauna account.
-
Which endpoint would you like to set as default? The
cloud-*
endpoint for your preferred Region Group. For example, to use the US Region Group, usecloud-us
.
fauna cloud-login
requires an email and password login. If you log in to Fauna using GitHub or Netlify, you can enable email and password login using the Forgot Password workflow. -
-
Use the Fauna CLI to create a new database:
fauna create-database --environment='' chat-app
-
Push the schema to Fauna:
fauna schema push
When prompted, accept and stage the schema.
-
Check the status of the staged schema:
fauna schema status
-
When the status is
ready
, commit the staged schema to the database:fauna schema commit
When prompted, agree and commit the changes. The commit applies the staged schema to the database.
-
Open a shell session to run queries on your
chat-app
database:fauna shell chat-app
-
Create a new key for the
UnAuthenticatedRole
:Key.create({ role: 'UnAuthenticatedRole' })
{ id: "395112986085163081", coll: Key, ts: Time("2099-04-14T04:12:36.920Z"), secret: "fn...", role: "UnAuthenticatedRole" }
-
Create an .env file in the root directory of your project and save the generated key
NEXT_PUBLIC_FAUNA_UNAUTHENTICATED_SECRET=<your-key>
-
Run the application:
npm run dev
-
Open the app in your browser at http://localhost:3000
Review the application code
Viewing rooms in real time
The application is built using Next.js. In the home page /src/app/page.js, users can create chat rooms and join existing conversations.
The code uses Event Streaming to
subscribe to real-time changes to the Room
collection:
// ... Rest of the component's code
const response = await client.query(fql`Room.all().eventSource()`);
const eventSource = response.data;
if (!streamRef.current) {
streamRef.current = await client.stream(eventSource);
for await (const event of streamRef.current) {
switch (event.type) {
case "start":
console.log("Stream start", event);
break;
case "update":
case "add":
setExistingRooms(prevRooms => {
const existingRoom = prevRooms.find(room => room.id ===
event?.data.id);
return existingRoom ? prevRooms : [...prevRooms,
event?.data];
});
break;
case "remove":
console.log("Stream remove:", event);
// Handles removal of rooms
break;
case "error":
console.log("Stream error:", event);
// Handles errors
break;
}
}
// Close the stream when the component unmounts
return () => {
streamRef.current.close();
};
}
Real-Time Messaging
The core of a real-time chat application is its messaging functionality. You can find the code for chat functionality in the file:
const startMessageStream = async () => {
try {
const response = await streamClient.query(fql`
let roomRef = Room.byId(${id})
Message.byRoomRef(roomRef).eventSource()
`);
const eventSource = response.data;
if (!messageStream.current) {
messageStream.current = await streamClient.stream(eventSource)
.on("start", event => {
console.log("Stream start", event);
getExistingMessages();
})
.on("add", event => {
console.log("Stream add", event);
setMessages(prevMessages => {
const existingMessage =
prevMessages.find(msg => msg.id === event?.data.id);
return existingMessage ?
prevMessages : [...prevMessages, event?.data];
});
})
.on('error', event => {
console.log("Stream error:", event);
});
messageStream.current.start();
}
} catch (error) {
console.error("Error fetching data:", error);
}
};
The code snippet shows the core functionality of setting up a stream
to monitor changes in the Message
collection for a room
with the given id
.
User authentication
The sample app uses Fauna ABAC for user authentication. If you cloned the application from GitHub and ran the setup steps, you already have authentication set up. To learn more about Fauna authentication, follow the Build an end-user authentication system guide.
You can find the app’s authentication logic in src/app/authenticationform/page.js file:
const handleAuth = async (e) => {
e.preventDefault();
if (isLoginView) {
try {
const result = await client.query(fql`
Login(${username}, ${password})
`)
const userInfo = {
username: result?.data.user.username,
id: result?.data.user.id,
key: result?.data.cred.secret,
};
setCookieWithTTL("chat-loggedin",
JSON.stringify(userInfo), 1440 * 60 * 1000);
router.push('/');
} catch (error) {
console.log(error)
}
} else {
try {
const result = await client.query(fql`
let user = User.byUsername(${username}).isEmpty()
if(user == true) {
Signup(${username}, ${password})
} else {
let message = "This username is already taken, select another"
message
}
`)
if (result.data == "This username is already taken, select another") {
alert("This username is already taken, select another");
setUsername('')
} else {
setIsLoginView(true);
setUsername('');
setPassword('');
alert('Account created, please login now')
}
} catch (error) {
console.log(error)
}
}
};
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!