In this tutorial, you will create a Real-time chat application built with React Native and Strapi CMS as the backend.
Strapi is a headless CMS that helps you quickly get your API and content management up and running. React Native is a tool that allows the creation of cross-platform mobile applications using React and JavaScript.
You will need the following to get started:
The code for this tutorial is available on my GitHub repository; feel free to clone it to get started.
To get started, we'll set up a new Strapi project. Strapi CMS provides you with a command-line interface (CLI) that simplifies the process of creating and managing your projects:
npx create-strapi-app my-chat-backend
The above command will scaffold a new Strapi content management system project and install the required Node.js dependencies. Please ensure that you choose the "quickstart" installation type.
Fill out the forms to create your administrator user account.
Next, we'll install the strapi-plugin-io and socket.io packages, which we'll use for real-time communication in our chat application. Press CTRL + C (Window or Linux) or CMD + C(Mac) to quit the server and run the commands:
cd my-chat-backend
npm install strapi-plugin-io socket.io
After installation, start the Strapi server with the command:
npm run develop
Create a React Native application with the command below:
npx create-expo-app chat-app --template blank
cd chat-app
This command will create a new React Native application using expo in the chat-app
directory. The --template
option instructs the expo to install the minimum required libraries.
Now, install the required dependencies for your React Native application.
1expo install @react-native-async-storage/async-storage
2expo install @react-native-community/masked-view
3expo install @react-navigation/native @react-navigation/stack
4npm install axios date-fns socket.io-client
In your Strapi administrator, create a collection called message
with the fields content
and timestamp
of type Long text and DateTime, respectively.
The collection type represents messages that users can send each other, and the content is the actual message the user sent.
We'll create a one-to-many relationship between the User and Message collections to associate messages with their respective senders and recipients.
In the Message collection, add new fields called sender and recipient of type Relation. Set the relation type to 1:N (one-to-many) and select the users-permissions collection as their target. Your Message collection should look like the screenshot below:
Check out this blog post to learn more about relationships in Strapi: Understanding and Using Relations in Strapi.
To allow users to read and send messages, as well as see other users chat with, you need to grant the Authenticated role create, find, and findOne access to the Message collection and find access to the User collection. This configuration will ensure that only authenticated (logged in) users can view messages, see other registered users, and send messages. To do this, navigate to Settings > Roles > Authenticated. In Permissions, check the create, find, and findOne boxes for the Message collection and the find box for the User collection.
Then scroll down to Users-permission, check the find and findOne boxes in the User tab, and click on the Save button.
Strapi provides built-in authentication and authorization features that you can use for your Strapi projects. You can access these endpoints at the following URLs:
/auth/local/register
(for user registration)/auth/local
(for user login)/users/me
(to fetch the current authenticated user)/auth/logout
(for user logout)Create a new directory called context
and a context/AuthContext.js
file in your chat-app
directory. Add the code snippet below:
1import React, { createContext, useContext, useState } from "react";
2import { backendBaseUrl } from "../services/WebSocketService";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4
5const AuthContext = createContext();
6
7export const AuthProvider = ({ children }) => {
8 const [authError, setAuthError] = useState("");
9 const [isAuthenticated, setIsAuthenticated] = useState(false);
10 const [activeUser, setActiveUser] = useState(null);
11
12 const saveToken = async (token) => {
13 try {
14 await AsyncStorage.setItem("authToken", token);
15 } catch (error) {
16 console.error("Error saving token:", error);
17 }
18 };
19
20 const getToken = async () => {
21 try {
22 const token = await AsyncStorage.getItem("authToken");
23 return token;
24 } catch (error) {
25 console.error("Error getting token:", error);
26 return null;
27 }
28 };
29
30
31 const clearToken = async () => {
32 try {
33 await AsyncStorage.removeItem("authToken");
34 } catch (error) {
35 console.error("Error clearing token:", error);
36 }
37 };
38 const register = async (username, email, password) => {
39 try {
40 const response = await fetch(`${backendBaseUrl}/ap/auth/local/register`, {
41 method: "POST",
42 headers: {
43 "Content-Type": "application/json",
44 },
45 body: JSON.stringify({
46 username: username,
47 email: email,
48 password: password,
49 }),
50 });
51 const responseData = await response.json();
52 if (responseData.jwt) {
53 return true;
54 } else {
55 setAuthError("Invalid details");
56 return false;
57 }
58 } catch (error) {
59 setAuthError("Error creating user");
60 return false;
61 }
62 };
63
64 const login = async (email, password) => {
65 try {
66 const response = await fetch(`${backendBaseUrl}/api/auth/local/`, {
67 method: "POST",
68 headers: {
69 "Content-Type": "application/json",
70 },
71 body: JSON.stringify({
72 identifier: email,
73 password: password,
74 }),
75 });
76 const responseData = await response.json();
77 if (responseData.jwt) {
78 setIsAuthenticated(true);
79 saveToken(responseData.jwt);
80 setActiveUser(responseData.user)
81 return true;
82 } else {
83 setAuthError("Incorrect email or password");
84 return false;
85 }
86 } catch (error) {
87 console.log(error);
88 setAuthError("Error occurred");
89 return false;
90 }
91 };
92
93 const logout = () => {
94 saveToken(null);
95 setIsAuthenticated(false);
96 };
97
98 return (
99 <AuthContext.Provider
100 value={{ isAuthenticated, register, login, logout, authError, getToken, clearToken, activeUser }}
101 >
102 {children}
103 </AuthContext.Provider>
104 );
105};
106
107export const useAuth = () => {
108 return useContext(AuthContext);
109};
This code creates an authentication context using the context API in React. The AuthContext
is created using the createContext()
method and the AuthProvider
component acts as the provider for the authentication context.
Inside the AuthProvider
, we have created the following:
authError
: Saves all the authentication errors that may happen during registration or login. isAuthenticated
: Specifies whether the user is currently online, that is, whether they are logged in to the application. activeUser
: This class contains details about the user who is currently using the application. saveToken
: This function saves the authentication token to the device’s local storage using the AsyncStorage. getToken
Extracts the authentication token from the device’s memory storage, which is securely kept in the configurations. clearToken
: Logs the user out, which essentially erases the token stored in the device’s database. register
: This function sends a POST request to the backend API to register a new user with the username
, email
address, and password
.login
: Sends an HTTP POST request to the backend API with the fields email
and password
for the user's basic authentication. If the login is successful and a JWT token is received, it sets isAuthenticated
to true. It then saves the token, sets the activeUser
, and returns true; if the token is not received, it sets an authentication error and returns false. logout
: Invalidates the authentication token and makes isAuthenticated
value equal to false. useAuth
is created as the counterpart of AuthContext
, which enables other components to import the context easily by using useContext(AuthContext)
.Next, create a screens
folder in the chat-app
directory. Create LoginScreen.js
and RegisterScreen.js
files in the 'screens' directory. Add the code snippet to the LoginScreen.js
file to allow users to log in to the login
function from the AuthContext
:
1import React, { useState, useEffect } from "react";
2import { View, Text, TextInput, Button, StyleSheet } from "react-native";
3import { useAuth } from "../context/AuthContext";
4
5const LoginScreen = ({ navigation }) => {
6 const { login, authError, isAuthenticated } = useAuth();
7 const [email, setEmail] = useState("");
8 const [password, setPassword] = useState("");
9
10 useEffect(() => {
11 if (isAuthenticated) {
12 navigation.navigate("Friends");
13 }
14 }, [isAuthenticated, navigation]);
15
16 return (
17 <View style={styles.container}>
18 <Text style={styles.title}>Login to Meet New Friends</Text>
19 <TextInput
20 style={styles.input}
21 placeholder="Email"
22 value={email}
23 onChangeText={setEmail}
24 />
25 <TextInput
26 style={styles.input}
27 placeholder="Password"
28 value={password}
29 onChangeText={setPassword}
30 secureTextEntry
31 />
32 <Text>{authError}</Text>
33 <Button
34 title="Login"
35 onPress={() => {
36 login(email, password);
37 }}
38 />
39 <Button
40 styles={styles.loginButton}
41 title="Don't have an account? Register"
42 onPress={() => navigation.navigate("Register")}
43 ></Button>
44 </View>
45 );
46};
47
48const styles = StyleSheet.create({
49 container: {
50 flex: 1,
51 justifyContent: "center",
52 alignItems: "center",
53 },
54 input: {
55 width: "80%",
56 height: 40,
57 borderWidth: 1,
58 borderColor: "gray",
59 marginVertical: 10,
60 paddingHorizontal: 12,
61 },
62 loginButton: {
63 backgroundColor: "#4554",
64 },
65 title: {
66 fontSize: 24,
67 fontWeight: "bold",
68 marginBottom: 16,
69 },
70});
71
72export default LoginScreen;
The above code has a button that links to the register screen. We have not created that yet, so the navigation will not work.
Update the App.js
file to render the LoginScreen
component.
1import LoginScreen from "./screens/LoginScreen";
2
3export default function App() {
4 return <LoginScreen />;
5}
Now add the code snippet below to the RegisterScreen.js
file to allow users to log in using the register
function from the AuthContext
:
1import React, { useState, useEffect } from "react";
2import { View, Text, TextInput, Button, StyleSheet } from "react-native";
3import { useAuth } from "../context/AuthContext";
4
5const RegisterScreen = ({ navigation }) => {
6 const [username, setUsername] = useState("");
7 const [email, setEmail] = useState("");
8 const [password, setPassword] = useState("");
9 const { register, authError, isAuthenticated } = useAuth();
10 useEffect(() => {
11 if (isAuthenticated) {
12 navigation.navigate("Chat");
13 }
14 }, [isAuthenticated, navigation]);
15
16 return (
17 <View style={styles.container}>
18 <Text style={styles.title}>Register to continue</Text>
19 <TextInput
20 style={styles.input}
21 placeholder="Username"
22 value={username}
23 onChangeText={setUsername}
24 />
25 <TextInput
26 style={styles.input}
27 placeholder="Email"
28 value={email}
29 onChangeText={setEmail}
30 />
31 <TextInput
32 style={styles.input}
33 placeholder="Password"
34 value={password}
35 onChangeText={setPassword}
36 secureTextEntry
37 />
38 <Text>{authError}</Text>
39 <Button
40 title="Register"
41 onPress={() => {
42 if (register(username, email, password)) {
43 navigation.navigate("Login");
44 }
45 }}
46 />
47 </View>
48 );
49};
50
51const styles = StyleSheet.create({
52 container: {
53 flex: 1,
54 justifyContent: "center",
55 alignItems: "center",
56 },
57 input: {
58 width: "80%",
59 height: 40,
60 borderWidth: 1,
61 borderColor: "gray",
62 marginVertical: 10,
63 paddingHorizontal: 10,
64 },
65 title: {
66 fontSize: 24,
67 fontWeight: "bold",
68 marginBottom: 16,
69 },
70});
71
72export default RegisterScreen;
Next, create a common/Navigation.js
file and add the code snippets below to create the app navigation:
1import React from "react";
2import { NavigationContainer } from "@react-navigation/native";
3import { createStackNavigator } from "@react-navigation/stack";
4
5import LoginScreen from "../screens/LoginScreen";
6import RegisterScreen from "../screens/RegisterScreen";
7import { AuthProvider } from "../context/AuthContext";
8
9const Stack = createStackNavigator();
10
11const Navigation = () => (
12 <NavigationContainer>
13 <AuthProvider>
14 <Stack.Navigator>
15 <Stack.Screen
16 name="Login"
17 component={LoginScreen}
18 options={{ headerShown: false }}
19 />
20 <Stack.Screen name="Register" component={RegisterScreen} />
21 </Stack.Navigator>
22 </AuthProvider>
23 </NavigationContainer>
24);
25
26export default Navigation;
This code establishes the basic navigation layout for the application where the LoginScreen
is the initial screen and the RegisterScreen
is accessible from the LoginScreen
. Wraps the navigation stack, making the authentication context available on all the screens through the stack.
Then update the App.js
file to render the Navigation
component:
1import Navigation from "./common/Navigation";
2export default function App() {
3 return <Navigation />;
4}
Now you can navigate to the RegisterScreen
and back, log in, and register users.
The strapi-plugin-io
package we installed earlier provides an easy way to integrate WebSockets with our Strapi application. To use it, open the ./config/plugin.js
file and configure it by replacing the code inside with the following code:
1module.exports = () => ({
2 io: {
3 enabled: true,
4 config: {
5 contentTypes: ['api::message.message'],
6 },
7 },
8});
With the above configuration, the strapi-plugin-io
plugin will listen for all supported events on the Message content type.
Create a new file in the screens
folder named FriendsScreen.js
, to list the active users so they can chat with one another.
1import React, { useEffect, useState } from "react";
2import {
3 View,
4 Text,
5 TouchableOpacity,
6 FlatList,
7 SafeAreaView,
8 StyleSheet,
9} from "react-native";
10import { useNavigation } from "@react-navigation/native";
11import { backendBaseUrl } from "../services/WebSocketService";
12import { useAuth } from "../context/AuthContext";
13
14const FriendsScreen = () => {
15 const navigation = useNavigation();
16 const [users, setUsers] = useState([]);
17 const { isAuthenticated, activeUser, getToken } = useAuth();
18
19 useEffect(() => {
20 if (!isAuthenticated) {
21 navigation.navigate("Login");
22 }
23 }, [isAuthenticated, navigation]);
24
25 const fetchUsers = async () => {
26 const token = await getToken();
27 try {
28 const response = await fetch(
29 `${backendBaseUrl}/api/users?filters[id][$ne]=${activeUser.id}`,
30 {
31 headers: {
32 Authorization: `Bearer ${token}`,
33 "Content-Type": "application/json",
34 },
35 }
36 );
37 if (!response.ok) {
38 throw new Error(`HTTP error! status: ${response.status}`);
39 }
40 const usersData = await response.json();
41 setUsers(usersData);
42 } catch (error) {
43 console.error("Error fetching users:", error);
44 }
45 };
46
47 useEffect(() => {
48 if (activeUser) {
49 fetchUsers();
50 }
51 }, [activeUser]);
52
53 const handleFriendPress = (friendId) => {
54 navigation.navigate("Chat", { friendId });
55 };
56
57 const renderFriendItem = ({ item }) => {
58 return (
59 <TouchableOpacity onPress={() => handleFriendPress(item.id)}>
60 <View style={{ padding: 16 }}>
61 <Text>{item.username}</Text>
62 </View>
63 </TouchableOpacity>
64 );
65 };
66
67 return (
68 <SafeAreaView style={styles.container}>
69 <View style={styles.content}>
70 <Text style={styles.title}>Friend Lists</Text>
71 <FlatList
72 data={users}
73 renderItem={renderFriendItem}
74 keyExtractor={(item) => item.id.toString()}
75 contentContainerStyle={styles.flatList}
76 />
77 </View>
78 </SafeAreaView>
79 );
80};
81
82const styles = StyleSheet.create({
83 container: {
84 flex: 1,
85 backgroundColor: "#fff",
86 },
87 content: {
88 flex: 1,
89 margin: 16,
90 },
91 title: {
92 fontSize: 24,
93 fontWeight: "bold",
94 marginBottom: 16,
95 },
96 flatList: {
97 flexGrow: 1,
98 },
99});
100
101export default FriendsScreen;
The above FriendsScreen
component is a protected screen, so we check to see if the user has logged in using the isAuthenticated
variable. Then we send a GET
request to the /api/users
endpoint in the fetchUsers
function to get all the users, excluding those currently logged in, and list them in the FlatList
component. For each of the users, we added a navigation.navigate("Chat")
to allow users to navigate to the chat screen(We have not created this screen yet) in the handleFriendPress
function.
Then, update the Navigation.js
to add the FriendsScreen
screen to the navigation stack.
1import React from "react";
2import { NavigationContainer } from "@react-navigation/native";
3import { createStackNavigator } from "@react-navigation/stack";
4
5import LoginScreen from "../screens/LoginScreen";
6import RegisterScreen from "../screens/RegisterScreen";
7import { AuthProvider } from "../context/AuthContext";
8import FriendsScreen from "../screens/FriendsScreen";
9
10const Stack = createStackNavigator();
11
12const Navigation = () => (
13 <NavigationContainer>
14 <AuthProvider>
15 <Stack.Navigator>
16 <Stack.Screen
17 name="Friends"
18 component={FriendsScreen}
19 options={{ headerShown: false }}
20 />
21 <Stack.Screen
22 name="Login"
23 component={LoginScreen}
24 options={{ headerShown: false }}
25 />
26 <Stack.Screen name="Register" component={RegisterScreen} />
27 <Stack.Screen
28 name="Chat"
29 component={ChatScreen}
30 />
31 </Stack.Navigator>
32 </AuthProvider>
33 </NavigationContainer>
34);
35
36export default Navigation;
You will be navigated to the friend's screen when you log in.
To allow users to chat with each other by sending and receiving messages, create a new file named ChatScreen.js
in the screens directory. First, fetch all the messages once the screen loads so users can see their previous conversations and listen to new incoming messages.
1import React, { useState, useEffect } from "react";
2import {
3 View,
4 TextInput,
5 FlatList,
6 Text,
7 StyleSheet,
8 TouchableOpacity,
9 SafeAreaView,
10 ScrollView,
11} from "react-native";
12import { WebSocketService } from "../services/WebSocketService";
13import { format } from "date-fns";
14import { backendBaseUrl } from "../services/WebSocketService";
15import { useAuth } from "../context/AuthContext";
16import { useRoute } from "@react-navigation/native";
17
18const ChatScreen = ({ navigation }) => {
19 const [messages, setMessages] = useState([]);
20 const [messageInput, setMessageInput] = useState("");
21 const { isAuthenticated, activeUser, getToken } = useAuth();
22 const route = useRoute();
23 const { friendId } = route.params;
24
25 useEffect(() => {
26 if (!isAuthenticated) {
27 navigation.navigate("Login");
28 }
29 }, [isAuthenticated, navigation]);
30
31 useEffect(() => {
32 let socket;
33
34 const connectWebSocket = async () => {
35 const token = await getToken();
36 if (token) {
37 socket = WebSocketService(token);
38
39 socket.on("connect", () => {
40 console.log("Connected to WebSocket");
41 });
42
43 socket.on("message:create", (message) => {
44 setMessages((prevMessages) => [...prevMessages, message.data]);
45 });
46 }
47 };
48
49 fetchMessages();
50 connectWebSocket();
51
52 return () => {
53 if (socket) {
54 socket.off("message:create");
55 socket.disconnect();
56 }
57 };
58 }, []);
59
60 const fetchMessages = async () => {
61 const token = await getToken();
62 try {
63 const response = await fetch(
64 `${backendBaseUrl}/api/messages?populate=*&filters[$or][0][sender][id][$eq]=${friendId}&filters[$or][0][recipient][id][$eq]=${activeUser.id}&filters[$or][1][sender][id][$eq]=${activeUser.id}&filters[$or][1][recipient][id][$eq]=${friendId}`,
65 {
66 headers: {
67 Authorization: `Bearer ${token}`,
68 "Content-Type": "application/json",
69 },
70 }
71 );
72 const responseData = await response.json();
73 setMessages(responseData.data);
74 } catch (error) {
75 console.error("Error fetching messages:", error);
76 }
77 };
78
79 return (
80 <SafeAreaView style={styles.container}>
81 <ScrollView style={styles.messageContainer}>
82 {messages.map((item, index) => (
83 <View
84 key={item.id.toString()}
85 style={[
86 styles.message,
87 item?.attributes?.sender?.data?.id === activeUser.id
88 ? styles.sentMessage
89 : styles.receivedMessage,
90 ]}
91 >
92 <Text style={styles.messageText}>{item.attributes.content}</Text>
93 <Text style={styles.timestamp}>{item.attributes.timestamp}</Text>
94 </View>
95 ))}
96 </ScrollView>
97 <View style={styles.inputContainer}>
98 <TextInput
99 style={styles.input}
100 value={messageInput}
101 onChangeText={(text) => {
102 setMessageInput(text);
103 }}
104 placeholder="Type a message..."
105 />
106 <TouchableOpacity style={styles.sendButton}>
107 <Text style={styles.sendButtonText}>Send</Text>
108 </TouchableOpacity>
109 </View>
110 </SafeAreaView>
111 );
112};
113
114const styles = StyleSheet.create({
115 container: {
116 flex: 1,
117 backgroundColor: "#f2f2f2",
118 },
119 messageContainer: {
120 flex: 1,
121 paddingHorizontal: 16,
122 paddingTop: 16,
123 },
124 message: {
125 backgroundColor: "#fff",
126 borderRadius: 16,
127 padding: 12,
128 marginVertical: 8,
129 maxWidth: "80%",
130 },
131 sentMessage: {
132 alignSelf: "flex-end",
133 backgroundColor: "#0084ff",
134 color: "#fff",
135 },
136 receivedMessage: {
137 alignSelf: "flex-start",
138 },
139 messageText: {
140 fontSize: 16,
141 lineHeight: 22,
142 },
143 timestamp: {
144 fontSize: 12,
145 color: "#666",
146 marginTop: 4,
147 },
148 inputContainer: {
149 flexDirection: "row",
150 alignItems: "center",
151 backgroundColor: "#fff",
152 paddingHorizontal: 16,
153 paddingVertical: 8,
154 },
155 input: {
156 flex: 1,
157 fontSize: 16,
158 paddingVertical: 8,
159 color: "#333",
160 },
161 sendButton: {
162 backgroundColor: "#0084ff",
163 borderRadius: 16,
164 paddingHorizontal: 16,
165 paddingVertical: 8,
166 marginLeft: 8,
167 },
168 sendButtonText: {
169 color: "#fff",
170 fontSize: 16,
171 },
172});
173
174export default ChatScreen;
In the above code, we first check if the user is authenticated and take them back to the login screen if they are not. Then, we defined a fetchMessages
function that filters all the messages between the active user(the user logged in) and the user they clicked to chat with. We then make a call to the function in the useEffect
hook to fetch the messages once this component mounts. We listen to the create
event when a socket is connected to allow users to receive new messages in real time. So with the help of the strapi-plugin-io
plugin, we can listen to any event in the Message collection, like when someone creates a new message, the recipient gets it immediately.
Next, update the ChatScreen
code to add a function for users to send a new message.
1import React, { useState, useEffect } from "react";
2import {
3 View,
4 TextInput,
5 FlatList,
6 Text,
7 StyleSheet,
8 TouchableOpacity,
9 SafeAreaView,
10 ScrollView,
11} from "react-native";
12import { WebSocketService } from "../services/WebSocketService";
13import { format } from "date-fns";
14import { backendBaseUrl } from "../services/WebSocketService";
15import { useAuth } from "../context/AuthContext";
16import { useRoute } from "@react-navigation/native";
17
18const ChatScreen = ({ navigation }) => {
19 const [messages, setMessages] = useState([]);
20 const [messageInput, setMessageInput] = useState("");
21 const { isAuthenticated, activeUser } = useAuth();
22 const route = useRoute();
23 const { friendId } = route.params;
24
25 // ... previous code goes here
26
27 const sendMessage = async () => {
28 if (messageInput.trim() === "") return;
29 const newMessage = {
30 data: {
31 content: messageInput,
32 sender: activeUser.id,
33 recipient: friendId,
34 timestamp: formattedDate(),
35 },
36 };
37
38 try {
39 const token = await getToken();
40 await fetch(`${backendBaseUrl}/api/messages?populate=*`, {
41 method: "POST",
42 headers: {
43 Authorization: `Bearer ${token}`,
44 "Content-Type": "application/json",
45 },
46 body: JSON.stringify(newMessage),
47 });
48
49 setMessageInput("");
50 } catch (error) {
51 console.error("Error sending messages:", error);
52 }
53 };
54
55 const formattedDate = () => {
56 const currentDate = new Date();
57 return format(currentDate, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
58 };
59
60 return (
61 <SafeAreaView style={styles.container}>
62 <ScrollView style={styles.messageContainer}>
63 {messages.map((item, index) => (
64 <View
65 key={item.id.toString()}
66 style={[
67 styles.message,
68 item?.attributes?.sender?.data?.id === activeUser.id
69 ? styles.sentMessage
70 : styles.receivedMessage,
71 ]}
72 >
73 <Text style={styles.messageText}>{item.attributes.content}</Text>
74 <Text style={styles.timestamp}>{item.attributes.timestamp}</Text>
75 </View>
76 ))}
77 </ScrollView>
78 <View style={styles.inputContainer}>
79 <TextInput
80 style={styles.input}
81 value={messageInput}
82 onChangeText={(text) => {
83 setMessageInput(text);
84 }}
85 placeholder="Type a message..."
86 />
87 <TouchableOpacity style={styles.sendButton} onPress={sendMessage}>
88 <Text style={styles.sendButtonText}>Send</Text>
89 </TouchableOpacity>
90 </View>
91 </SafeAreaView>
92 );
93};
94
95//... styles goes here
96
97export default ChatScreen;
Here, we added a sendMessage
function that sends a POST request to our Strapi backend to create a new message. Once successful, the strapi-plugin-io
library picks up the event, triggers the creation of events, and adds a new message to the array of messages between the users(the sender and recipient).
For the socket connection to work, we need to set up a socket connection between our React Native app and Strapi backend using the socket.io-client
package we installed earlier. Create a services
folder in the chat-app
folder and create a WebSocketService.js
.
Add the code snippet below:
1import io from "socket.io-client";
2import { Platform } from "react-native";
3
4export const backendBaseUrl =
5 Platform.OS === "android" ? "http://10.0.2.2:1337" : "http://localhost:1337";
6
7export const WebSocketService = (token) => {
8 const socket = io(backendBaseUrl, {
9 auth: {
10 token: token,
11 },
12 });
13 return socket;
14};
The above establishes a WebSocket connection from our React Native app to our backend server. It uses the user's authorization token to give users access to the Message collection and considers the different ways to access localhost on Android emulators and iOS. We can access localhost on Android emulators at http://10.0.2.2:1337
and iPhone, Mac, and Web emulators at http://localhost:1337
.
Now refresh your application, click on the user, and start chatting.
Here is a demo of what our app looks like.
In this article, we explored building a Real-Time chat application with Strapi and React Native. We learned how to create a collection in Strapi and implement authentication using the Strapi built-in JWT authentication. We then built out the chatting application using socket.io with the help of the strapi-plugin-io
plugin.
Now that you know this, how would you use Strapi in your next React Native project? Perhaps you can enhance the application by adding features like Push notifications, Image messages, and User profiles.
With the help of the extensibility, flexibility, and real-time capabilities of Strapi along with plugins like strapi-plugin-io
, you can create applications that are not just limited to simple chatting. Strapi’s strong backend, coupled with the high-performance, cross-platform UI of React Native, makes this stack ideal for creating new-generation, real-time mobile apps.
I am Software Engineer and Technical Writer. Proficient Server-side scripting and Database setup. Agile knowledge of Python, NodeJS, ReactJS, and PHP. When am not coding, I share my knowledge and experience with the other developers through technical articles