Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
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.
1
2
3
4
expo install @react-native-async-storage/async-storage
expo install @react-native-community/masked-view
expo install @react-navigation/native @react-navigation/stack
npm 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import React, { createContext, useContext, useState } from "react";
import { backendBaseUrl } from "../services/WebSocketService";
import AsyncStorage from "@react-native-async-storage/async-storage";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authError, setAuthError] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [activeUser, setActiveUser] = useState(null);
const saveToken = async (token) => {
try {
await AsyncStorage.setItem("authToken", token);
} catch (error) {
console.error("Error saving token:", error);
}
};
const getToken = async () => {
try {
const token = await AsyncStorage.getItem("authToken");
return token;
} catch (error) {
console.error("Error getting token:", error);
return null;
}
};
const clearToken = async () => {
try {
await AsyncStorage.removeItem("authToken");
} catch (error) {
console.error("Error clearing token:", error);
}
};
const register = async (username, email, password) => {
try {
const response = await fetch(`${backendBaseUrl}/ap/auth/local/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username,
email: email,
password: password,
}),
});
const responseData = await response.json();
if (responseData.jwt) {
return true;
} else {
setAuthError("Invalid details");
return false;
}
} catch (error) {
setAuthError("Error creating user");
return false;
}
};
const login = async (email, password) => {
try {
const response = await fetch(`${backendBaseUrl}/api/auth/local/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
identifier: email,
password: password,
}),
});
const responseData = await response.json();
if (responseData.jwt) {
setIsAuthenticated(true);
saveToken(responseData.jwt);
setActiveUser(responseData.user)
return true;
} else {
setAuthError("Incorrect email or password");
return false;
}
} catch (error) {
console.log(error);
setAuthError("Error occurred");
return false;
}
};
const logout = () => {
saveToken(null);
setIsAuthenticated(false);
};
return (
<AuthContext.Provider
value={{ isAuthenticated, register, login, logout, authError, getToken, clearToken, activeUser }}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
return useContext(AuthContext);
};
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
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React, { useState, useEffect } from "react";
import { View, Text, TextInput, Button, StyleSheet } from "react-native";
import { useAuth } from "../context/AuthContext";
const LoginScreen = ({ navigation }) => {
const { login, authError, isAuthenticated } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
if (isAuthenticated) {
navigation.navigate("Friends");
}
}, [isAuthenticated, navigation]);
return (
<View style={styles.container}>
<Text style={styles.title}>Login to Meet New Friends</Text>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Text>{authError}</Text>
<Button
title="Login"
onPress={() => {
login(email, password);
}}
/>
<Button
styles={styles.loginButton}
title="Don't have an account? Register"
onPress={() => navigation.navigate("Register")}
></Button>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
input: {
width: "80%",
height: 40,
borderWidth: 1,
borderColor: "gray",
marginVertical: 10,
paddingHorizontal: 12,
},
loginButton: {
backgroundColor: "#4554",
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 16,
},
});
export 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.
1
2
3
4
5
import LoginScreen from "./screens/LoginScreen";
export default function App() {
return <LoginScreen />;
}
Now add the code snippet below to the RegisterScreen.js
file to allow users to log in using the register
function from the AuthContext
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React, { useState, useEffect } from "react";
import { View, Text, TextInput, Button, StyleSheet } from "react-native";
import { useAuth } from "../context/AuthContext";
const RegisterScreen = ({ navigation }) => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { register, authError, isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated) {
navigation.navigate("Chat");
}
}, [isAuthenticated, navigation]);
return (
<View style={styles.container}>
<Text style={styles.title}>Register to continue</Text>
<TextInput
style={styles.input}
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Text>{authError}</Text>
<Button
title="Register"
onPress={() => {
if (register(username, email, password)) {
navigation.navigate("Login");
}
}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
input: {
width: "80%",
height: 40,
borderWidth: 1,
borderColor: "gray",
marginVertical: 10,
paddingHorizontal: 10,
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 16,
},
});
export default RegisterScreen;
Next, create a common/Navigation.js
file and add the code snippets below to create the app navigation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import LoginScreen from "../screens/LoginScreen";
import RegisterScreen from "../screens/RegisterScreen";
import { AuthProvider } from "../context/AuthContext";
const Stack = createStackNavigator();
const Navigation = () => (
<NavigationContainer>
<AuthProvider>
<Stack.Navigator>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="Register" component={RegisterScreen} />
</Stack.Navigator>
</AuthProvider>
</NavigationContainer>
);
export 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:
1
2
3
4
import Navigation from "./common/Navigation";
export default function App() {
return <Navigation />;
}
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:
1
2
3
4
5
6
7
8
module.exports = () => ({
io: {
enabled: true,
config: {
contentTypes: ['api::message.message'],
},
},
});
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import React, { useEffect, useState } from "react";
import {
View,
Text,
TouchableOpacity,
FlatList,
SafeAreaView,
StyleSheet,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import { backendBaseUrl } from "../services/WebSocketService";
import { useAuth } from "../context/AuthContext";
const FriendsScreen = () => {
const navigation = useNavigation();
const [users, setUsers] = useState([]);
const { isAuthenticated, activeUser, getToken } = useAuth();
useEffect(() => {
if (!isAuthenticated) {
navigation.navigate("Login");
}
}, [isAuthenticated, navigation]);
const fetchUsers = async () => {
const token = await getToken();
try {
const response = await fetch(
`${backendBaseUrl}/api/users?filters[id][$ne]=${activeUser.id}`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const usersData = await response.json();
setUsers(usersData);
} catch (error) {
console.error("Error fetching users:", error);
}
};
useEffect(() => {
if (activeUser) {
fetchUsers();
}
}, [activeUser]);
const handleFriendPress = (friendId) => {
navigation.navigate("Chat", { friendId });
};
const renderFriendItem = ({ item }) => {
return (
<TouchableOpacity onPress={() => handleFriendPress(item.id)}>
<View style={{ padding: 16 }}>
<Text>{item.username}</Text>
</View>
</TouchableOpacity>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Friend Lists</Text>
<FlatList
data={users}
renderItem={renderFriendItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.flatList}
/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
content: {
flex: 1,
margin: 16,
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 16,
},
flatList: {
flexGrow: 1,
},
});
export 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import LoginScreen from "../screens/LoginScreen";
import RegisterScreen from "../screens/RegisterScreen";
import { AuthProvider } from "../context/AuthContext";
import FriendsScreen from "../screens/FriendsScreen";
const Stack = createStackNavigator();
const Navigation = () => (
<NavigationContainer>
<AuthProvider>
<Stack.Navigator>
<Stack.Screen
name="Friends"
component={FriendsScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen
name="Chat"
component={ChatScreen}
/>
</Stack.Navigator>
</AuthProvider>
</NavigationContainer>
);
export 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import React, { useState, useEffect } from "react";
import {
View,
TextInput,
FlatList,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
ScrollView,
} from "react-native";
import { WebSocketService } from "../services/WebSocketService";
import { format } from "date-fns";
import { backendBaseUrl } from "../services/WebSocketService";
import { useAuth } from "../context/AuthContext";
import { useRoute } from "@react-navigation/native";
const ChatScreen = ({ navigation }) => {
const [messages, setMessages] = useState([]);
const [messageInput, setMessageInput] = useState("");
const { isAuthenticated, activeUser, getToken } = useAuth();
const route = useRoute();
const { friendId } = route.params;
useEffect(() => {
if (!isAuthenticated) {
navigation.navigate("Login");
}
}, [isAuthenticated, navigation]);
useEffect(() => {
let socket;
const connectWebSocket = async () => {
const token = await getToken();
if (token) {
socket = WebSocketService(token);
socket.on("connect", () => {
console.log("Connected to WebSocket");
});
socket.on("message:create", (message) => {
setMessages((prevMessages) => [...prevMessages, message.data]);
});
}
};
fetchMessages();
connectWebSocket();
return () => {
if (socket) {
socket.off("message:create");
socket.disconnect();
}
};
}, []);
const fetchMessages = async () => {
const token = await getToken();
try {
const response = await fetch(
`${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}`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
const responseData = await response.json();
setMessages(responseData.data);
} catch (error) {
console.error("Error fetching messages:", error);
}
};
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.messageContainer}>
{messages.map((item, index) => (
<View
key={item.id.toString()}
style={[
styles.message,
item?.attributes?.sender?.data?.id === activeUser.id
? styles.sentMessage
: styles.receivedMessage,
]}
>
<Text style={styles.messageText}>{item.attributes.content}</Text>
<Text style={styles.timestamp}>{item.attributes.timestamp}</Text>
</View>
))}
</ScrollView>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={messageInput}
onChangeText={(text) => {
setMessageInput(text);
}}
placeholder="Type a message..."
/>
<TouchableOpacity style={styles.sendButton}>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f2f2f2",
},
messageContainer: {
flex: 1,
paddingHorizontal: 16,
paddingTop: 16,
},
message: {
backgroundColor: "#fff",
borderRadius: 16,
padding: 12,
marginVertical: 8,
maxWidth: "80%",
},
sentMessage: {
alignSelf: "flex-end",
backgroundColor: "#0084ff",
color: "#fff",
},
receivedMessage: {
alignSelf: "flex-start",
},
messageText: {
fontSize: 16,
lineHeight: 22,
},
timestamp: {
fontSize: 12,
color: "#666",
marginTop: 4,
},
inputContainer: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#fff",
paddingHorizontal: 16,
paddingVertical: 8,
},
input: {
flex: 1,
fontSize: 16,
paddingVertical: 8,
color: "#333",
},
sendButton: {
backgroundColor: "#0084ff",
borderRadius: 16,
paddingHorizontal: 16,
paddingVertical: 8,
marginLeft: 8,
},
sendButtonText: {
color: "#fff",
fontSize: 16,
},
});
export 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import React, { useState, useEffect } from "react";
import {
View,
TextInput,
FlatList,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
ScrollView,
} from "react-native";
import { WebSocketService } from "../services/WebSocketService";
import { format } from "date-fns";
import { backendBaseUrl } from "../services/WebSocketService";
import { useAuth } from "../context/AuthContext";
import { useRoute } from "@react-navigation/native";
const ChatScreen = ({ navigation }) => {
const [messages, setMessages] = useState([]);
const [messageInput, setMessageInput] = useState("");
const { isAuthenticated, activeUser } = useAuth();
const route = useRoute();
const { friendId } = route.params;
// ... previous code goes here
const sendMessage = async () => {
if (messageInput.trim() === "") return;
const newMessage = {
data: {
content: messageInput,
sender: activeUser.id,
recipient: friendId,
timestamp: formattedDate(),
},
};
try {
const token = await getToken();
await fetch(`${backendBaseUrl}/api/messages?populate=*`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(newMessage),
});
setMessageInput("");
} catch (error) {
console.error("Error sending messages:", error);
}
};
const formattedDate = () => {
const currentDate = new Date();
return format(currentDate, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
};
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.messageContainer}>
{messages.map((item, index) => (
<View
key={item.id.toString()}
style={[
styles.message,
item?.attributes?.sender?.data?.id === activeUser.id
? styles.sentMessage
: styles.receivedMessage,
]}
>
<Text style={styles.messageText}>{item.attributes.content}</Text>
<Text style={styles.timestamp}>{item.attributes.timestamp}</Text>
</View>
))}
</ScrollView>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={messageInput}
onChangeText={(text) => {
setMessageInput(text);
}}
placeholder="Type a message..."
/>
<TouchableOpacity style={styles.sendButton} onPress={sendMessage}>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
//... styles goes here
export 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import io from "socket.io-client";
import { Platform } from "react-native";
export const backendBaseUrl =
Platform.OS === "android" ? "http://10.0.2.2:1337" : "http://localhost:1337";
export const WebSocketService = (token) => {
const socket = io(backendBaseUrl, {
auth: {
token: token,
},
});
return socket;
};
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