How to Develop a Real-Time Chat App with Node.js and React Native: Complete Tutorial

Hamza KachachiHamza Kachachi
6 min read

Introduction

In today's digital age, chat applications have become an integral part of our communication. Whether it's for personal use or business purposes, having a reliable chat application can enhance the way we connect with others. In this tutorial, we'll walk you through the process of creating a chat app using Node.js for the backend and React Native for the frontend. By the end of this tutorial, you'll have a functional chat application that you can further customize and expand.

Setting Up the Environment

Installing Node.js and npm

First, you need to install Node.js and npm (Node Package Manager) on your computer. You can download the latest version of Node.js from the official website.

# Verify the installation
node -v
npm -v

Setting Up a New Node.js Project

Create a new directory for your project and initialize a new Node.js project:

mkdir chat-app
cd chat-app
npm init -y

Installing Necessary Packages

We'll need Express for the server, Socket.IO for real-time communication, and some other packages for our backend.

npm install express socket.io mongoose bcryptjs jsonwebtoken

Creating the Backend with Node.js

Setting Up Express.js

Create a new file server.js and set up a basic Express server:

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

const PORT = process.env.PORT || 3000;

app.use(express.json());

app.get('/', (req, res) => {
    res.send('Chat server is running');
});

server.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Creating the WebSocket Server with Socket.IO

Add the following code to handle WebSocket connections:

io.on('connection', (socket) => {
    console.log('New client connected');

    socket.on('disconnect', () => {
        console.log('Client disconnected');
    });
});

Building the REST API

We'll create endpoints for user authentication and message handling.

const User = require('./models/User');
const Message = require('./models/Message');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const JWT_SECRET = 'your_jwt_secret_key';

// User registration
app.post('/register', async (req, res) => {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new User({ username, password: hashedPassword });
    await user.save();
    res.status(201).send('User registered');
});

// User login
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (user && await bcrypt.compare(password, user.password)) {
        const token = jwt.sign({ userId: user._id }, JWT_SECRET);
        res.json({ token });
    } else {
        res.status(401).send('Invalid credentials');
    }
});

// Message handling
app.post('/messages', async (req, res) => {
    const { token, content } = req.body;
    const decoded = jwt.verify(token, JWT_SECRET);
    const message = new Message({ userId: decoded.userId, content });
    await message.save();
    res.status(201).send('Message sent');
});

Connecting to MongoDB

Set up Mongoose to connect to your MongoDB database:

mongoose.connect('mongodb://localhost:27017/chat', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
}).then(() => {
    console.log('Connected to MongoDB');
}).catch((err) => {
    console.error('Failed to connect to MongoDB', err);
});

Create models for User and Message:

// models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: { type: String, required: true, unique: true },
    password: { type: String, required: true },
});

module.exports = mongoose.model('User', userSchema);

// models/Message.js
const mongoose = require('mongoose');

const messageSchema = new mongoose.Schema({
    userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
    content: { type: String, required: true },
    timestamp: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Message', messageSchema);

Setting Up the React Native Project

Installing React Native CLI

Install React Native CLI globally on your computer:

npm install -g react-native-cli

Creating a New React Native Project

Create a new React Native project:

npx react-native init ChatApp
cd ChatApp

Installing Necessary Packages

We'll need the Socket.IO client and React Navigation for our frontend.

npm install socket.io-client @react-navigation/native @react-navigation/stack

Building the Chat Interface in React Native

Creating the User Interface

Create a basic chat screen and user authentication screens.

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import LoginScreen from './screens/LoginScreen';
import ChatScreen from './screens/ChatScreen';

const Stack = createStackNavigator();

const App = () => {
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen name="Login" component={LoginScreen} />
                <Stack.Screen name="Chat" component={ChatScreen} />
            </Stack.Navigator>
        </NavigationContainer>
    );
};

export default App;

Implementing User Authentication

Create the LoginScreen to handle user login:

// screens/LoginScreen.js
import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';

const LoginScreen = ({ navigation }) => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleLogin = () => {
        // Call your backend login API
    };

    return (
        <View>
            <Text>Login</Text>
            <TextInput placeholder="Username" value={username} onChangeText={setUsername} />
            <TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
            <Button title="Login" onPress={handleLogin} />
        </View>
    );
};

export default LoginScreen;

Connecting to the WebSocket Server

Connect to the WebSocket server in ChatScreen:

// screens/ChatScreen.js
import React, { useEffect, useState } from 'react';
import { View, TextInput, Button, Text, FlatList } from 'react-native';
import io from 'socket.io-client';

const ChatScreen = () => {
    const [message, setMessage] = useState('');
    const [messages, setMessages] = useState([]);
    const socket = io('http://localhost:3000');

    useEffect(() => {
        socket.on('message', (msg) => {
            setMessages((prevMessages) => [...prevMessages, msg]);
        });
    }, []);

    const sendMessage = () => {
        socket.emit('message', message);
        setMessage('');
    };

    return (
        <View>
            <FlatList
                data={messages}
                renderItem={({ item }) => <Text>{item}</Text>}
                keyExtractor={(item, index) => index.toString()}
            />
            <TextInput value={message} onChangeText={setMessage} />
            <Button title="Send" onPress={sendMessage} />
        </View>
    );
};

export default ChatScreen;

Integrating the Backend

with the Frontend

Fetching and Displaying Chat Messages

Modify ChatScreen to fetch and display chat messages:

import axios from 'axios';

// Fetch chat messages
useEffect(() => {
    const fetchMessages = async () => {
        const response = await axios.get('http://localhost:3000/messages');
        setMessages(response.data);
    };

    fetchMessages();
}, []);

Sending Messages from the App to the Server

Update the sendMessage function to send messages to the server:

const sendMessage = async () => {
    const token = await AsyncStorage.getItem('token');
    await axios.post('http://localhost:3000/messages', { token, content: message });
    socket.emit('message', message);
    setMessage('');
};

Handling Incoming Messages

Ensure the app handles incoming messages in real-time:

useEffect(() => {
    socket.on('message', (msg) => {
        setMessages((prevMessages) => [...prevMessages, msg]);
    });
}, []);

Testing and Debugging

Running the Node.js Server

Start your Node.js server:

node server.js

Running the React Native App

Run your React Native app on a simulator or device:

npx react-native run-android
# or
npx react-native run-ios

Debugging Common Issues

Ensure you handle common issues like server connection errors, incorrect API endpoints, and authentication failures. Use tools like React Native Debugger and console logs to troubleshoot problems.

Conclusion

In this tutorial, we've walked through the process of creating a chat application using Node.js for the backend and React Native for the frontend. We covered setting up the development environment, building the server with Express and Socket.IO, creating the user interface in React Native, and integrating the backend with the frontend. While this is a basic implementation, there's a lot of potential for customization and expansion. You can add features like user profiles, message timestamps, group chats, and more. Happy coding!

11
Subscribe to my newsletter

Read articles from Hamza Kachachi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hamza Kachachi
Hamza Kachachi

Hello! My namesake is Hamza Kachachi. I come from Fes, Morocco. I am engaged in creating powerful web and mobile applications with Application Interfaces in NodeJS and ReactJS. I am also able to use PHP, Laravel and MongoDB. Furthermore, I know both front-end and back-end programming languages enough to feel comfortable with Agile methodologies. In my free time I enjoy playing basketball, chess or football or simply listening to music. Let’s connect and create something amazing together!