When Fathul Fahmy began work on his final-year university project, he set out to build a fully functional application with real-world utility.
The result was The Barter App, a mobile-first platform where users exchange services, either directly or with optional monetary compensation. Think: "I'll design your logo if you help me fix my website."
Over the course of four months, Fathul brought this idea to life by building:
- A cross-platform mobile app
- A custom administrative web panel
- A Laravel-based backend API
- And a fully integrated real-time chat experience using Stream
That last piece, chat, played a surprisingly important role in helping The Barter App stand out. By using Stream, Fathul was able to integrate real-time messaging quickly and spend more time refining the app's core features.
The result: a polished, feature-rich product that earned him his university's Outstanding Final Year Project Award.
Scoping the Build
With just four months to deliver 18 distinct modules, Fathul had to make careful tradeoffs.
He wanted the app to include:
- Service discovery and listing
- Request management and negotiation
- Real-time messaging to chat and finalize terms
- Transaction handling
- User profiles and ratings
Building a real-time messaging system from scratch wasn't feasible, especially when features like service discovery, request workflows, and transaction management were the core of the app.
"Initially, I planned to implement WebSocket communication manually using Laravel on the backend and React Native on the frontend," Fathul shared. "However, after assessing the time investment required to learn, integrate, and scale such a solution, it became clear that a more efficient approach was necessary."
Choosing Stream for Real-Time Messaging
Fathul evaluated a few options for adding chat to his app. But Stream stood out for several key reasons:
- SDKs for both React Native and PHP
- Developer-centric documentation and tooling
- High-speed integration cycle
- Scalability out of the box
By signing up for a free Stream account, Fathul was able to access all the core functionality needed to ship a polished, scalable messaging experience in less than one week, without writing his own real-time infrastructure.
Here's how Fathul built The Barter App.
Developer Setup
Architecture Overview
- Frontend: React Native (Expo), using stream-chat-react-native
- Backend: Laravel, using stream-chat-php
- Stack Highlights: Expo Router, RESTful APIs, Stream's hosted messaging layer
Free Stream Account
Before proceeding with the integration, Fathul:
- Signed up for a free Stream account.
- Created a project/app on his Dashboard.
- Noted the API public and secret key, as these would be used in frontend and backend integrations.
Frontend Development
1. Install Stream Chat SDK
1npx expo install stream-chat-expo
2. Create a Chat Client Singleton
123import { StreamChat } from "stream-chat"; export const client = StreamChat.getInstance(process.env.EXPO_PUBLIC_STREAM_CHAT_KEY!);
3. Connect User on Login
12345678910111213141516171819const useLogin = (options?: Omit<UseMutationOptions<AuthResponse, Error, LoginInput>, "mutationFn">) => { // auth logic const setChatUser = async (data: AuthResponse) => { await client.connectUser( { id: String(data.user.id), }, data.chat_token, ); }; return useMutation({ mutationFn: loginFn, ...options, onSuccess: (response, ...rest) => { // auth logic setChatUser(response); }, }); };
4. List Conversations with All Users
123456789101112131415161718192021222324252627282930313233343536import React, { useMemo } from "react"; import { router } from "expo-router"; import { ChannelSort } from "stream-chat"; import { ChannelList } from "stream-chat-expo"; import { useUser } from "@/lib/auth/auth"; import { useChat } from "@/lib/stream-chat/chat-store"; const sort: ChannelSort = { last_message_at: -1 }; export const Conversations = () => { const { setChannel } = useChat(); const userQuery = useUser(); const user = userQuery.data; const memoizedFilters = useMemo( () => ({ members: { $in: [user?.id || ""] }, type: "messaging", last_message_at: { $gte: "2020-01-01T00:00:00.00Z" }, }), [user?.id], ); return ( <ChannelList filters={memoizedFilters} sort={sort} onSelect={(channel) => { setChannel(channel); router.push(`/chat/${channel.cid}`); }} /> ); };
This screen displays the list of active chat conversations, sorted by most recent activity.

5. Show a Conversation with a User
1234567891011121314151617181920212223242526import React from "react"; import { router } from "expo-router"; import { Channel, MessageInput, MessageList } from "stream-chat-expo"; import { useChat } from "@/lib/stream-chat/chat-store"; import { CustomAttachButton } from "./custom-attach-button"; export const Conversation = () => { const { channel, setThread } = useChat(); if (!channel) return null; return ( <Channel channel={channel} keyboardVerticalOffset={90} AttachButton={CustomAttachButton}> <MessageList onThreadSelect={(thread) => { setThread(thread); router.push(`/chat/${channel.cid}/thread/${thread?.cid}`); }} /> <MessageInput /> </Channel> ); };
Once a user selects a conversation, the Channel component renders a real-time chat view. Here, users can read and send messages, view threads, and engage in rich conversations.

Backend: Laravel + Stream PHP SDK
1. Install the SDK
1composer require get-stream/stream-chat
2. Create Base Controller or Util Functions
123456789101112131415161718public function upsertChatUser(mixed $user): void { $chat_client = new Client(config('app.stream_chat.key'), config('app.stream_chat.secret')); $chat_client->upsertUsers([ [ 'id' => (string) $user->id, 'name' => $user->name, 'role' => 'user', 'image' => $user->avatar['uri'], ], ]); } public function createChatToken($user_id): string { $chat_client = new Client(config('app.stream_chat.key'), config('app.stream_chat.secret')); $chat_token = $chat_client->createToken((string) $user_id); return $chat_token; }
3. Connect User on Login
12345678910111213141516171819202122232425public function login(AuthLoginRequest $request): JsonResponse { try { DB::beginTransaction(); // auth logic $user = auth()->user(); $this->upsertChatUser($user); $chat_token = $this->createChatToken($user->id); DB::commit(); return response()->apiSuccess('Logged in successfully', // other responses 'chat_token' => $chat_token, 'user' => auth()->user(), ]); } catch (\Exception $e) { DB::rollBack(); return response()->apiError('Failed to login', $e->getMessage()); } }
After logging in, users can navigate to the "Ongoing" tab, where they can view and manage their ongoing service exchanges, check deliverables, and jump straight into chat.

Overcoming Challenges
One technical challenge occurred when the chat attachment button didn't behave as expected in the React Native environment.
However, after raising a GitHub issue, Fathul received a quick and helpful response from a Stream contributor, which helped him resolve the issue without blocking development.
Fathul said, "This level of developer support solidified my confidence in Stream, not only as a product but also as a platform that understands and actively supports the development lifecycle."
Final Results
With Stream, Fathul shipped a polished messaging experience in just one week—leaving him free to focus on the rest of The Barter App.
"Stream enabled me to deliver a fully functional, real-time chat feature with minimal configuration and an exceptional developer experience. Given the time constraints of an academic project, this would have been unattainable through traditional WebSocket development," Fathul shared.
For students, startups, or solo developers aiming to implement chat with limited time and resources, Stream offers a scalable, robust, and developer-friendly solution.
Create your free account today.