Building a Persistent React Native Chat App: Part II — Server

Technologies: React Native, Redux/React-Redux, Gifted Chat, React Navigation, Socket.io, Sequelize, Postgres

If this is your first time reading this series, be sure to check out the first part:

And then when you’re done with part II, feel free to move on to part III:

If you’re reading this series for the first time, be sure to check out part I for more information about this project’s database.

Welcome back. Now that we’ve set up our database, we can create our server. We’ll start by creating an HTTP server that will listen on port 3000. After that, we’ll add our event listeners and emitters.

Let’s get started!

Let’s Build a Server!

Start by creating an index.js file in your main directory by typing touch index.js in your terminal. Under “scripts” in your package.json file, add “start”: “nodemon index.js” (or just "node index.js" if you don’t have nodemon installed).

In your newly created index.js file, let’s start by creating an HTTP server. Add:

const server = require('http').createServer.listen(3000);

Next, let’s import our models, create our socket server, and sync our database:

const conn = require('./db').conn;
const io = require('socket.io')(server);
const { User, Conversation, Message } = require('./db').models;
conn.sync({ logging: false, force: true });

Here, we’re using the HTTP server we made for the socket server. We’re also using ES6 destructuring to pull the models from our db directory’s exports. In conn.sync , I’m turning off the terminal logging and forcing the database to reset each time this runs. These are just my preferences, so feel free to play with these options as you see fit.

(Note: Be sure to remove { force: true } when you deploy your app. Otherwise your users will be in for a bad time.)

Socket Time!

Now on to Socket.io! I don’t know about you, but I LOVE sockets — there are just so many cool things you can do with them!

If you’re unfamiliar with sockets, they allow for open communication between a server and client. In normal HTTP, a server can only respond to requests from a client — it can’t initiate anything itself. With sockets, though, a server can emit events without a client request. That’s awesome! Using event listeners and emitters, we can do so much!

Since this is a private messaging app, we’ll need a way to tie the sockets to their various users. Let’s create a new object called mobileSockets. Later, we’ll create key-value pairs consisting of the users’ IDs and their corresponding socket IDs:

const conn = require('./db').conn;
const io = require('socket.io')(server);
const { User, Conversation, Message } = require('./db').models;
conn.sync({ logging: false, force: true });
const mobileSockets = {};

We’ll need our sockets to handle a few different events:

  • The first screen users will see on the Native app is a login screen where they can enter their name and password. We’re not really doing authentication in this tutorial, so we’ll just need an event listener to find or create a user on login.
  • On the next screen, users will be able to see a list of all the users they can chat with (excluding, of course, themselves).
  • On selecting a user to chat with, they’ll need to either open up an existing conversation or create a new one (which is where the findOrCreateConversation class method comes in).
  • Lastly, they’ll need to be able to send messages, which will be passed on by the server to the recipient.

Let’s set up our event listeners and emitters, which will all be wrapped within io.on(‘connection’). I’ll add the listeners step by step, and then walk through what each one is doing. First, let’s handle login:

io.on('connection', socket => {
socket.on('newUser', credentials => {
const { name, password } = credentials;
Promise.all([
User.findOrCreate({
where: {
name,
password
}
}),
User.findAll()
])
.then(([user, users]) => {
mobileSockets[user[0].id] = socket.id;
socket.emit('userCreated', { user: user[0], users });
socket.broadcast.emit('newUser', user[0]);
});
});
})

io.on(‘connection’) is listening for a connection event. On connection, it passes that particular instance’s socket object to the function, which we’ll then use to create all of our necessary emitters and listeners.

In socket.on(‘newUser’), we’re listening for a login event that’s emitted from the Native app and passing along the name and password as a single object. Then we’re using Promise.all to find or create the user as well as retrieve a list of all the existing users. (We’ll be filtering on the front end to exclude the logged-in user.) Promise.all returns an array, so in the next part of the promise chain, we’re using array destructuring to retrieve the values. Then, we’re:

  • creating the key-value socket pair I mentioned earlier,
  • emitting an event to the newly logged-in user containing the user object from the database as well as an array of all the existing users,
  • and finally emitting the new user to the rest of the users.

A few things to note:

  • First, in an actual app, you won’t want to emit the user object in this way, as you’ll want to prevent the password and other confidential information from being stored locally. Since this article doesn’t focus on authentication, I’m overlooking this issue.
  • Second, findOrCreate returns an array containing two values: the database object and a boolean value indicating whether the record was found or created. We just care about the object here, so that’s why we’re using user[0].
  • Lastly, the broadcast in socket.broadcast.emit will emit an event to every other socket except the one that initiated it.

Next, let’s create a listener that calls findOrCreateConversation and sends back all of the past messages in that conversation. Let’s take a peek at the code:

socket.on('chat', users => {
Conversation.findOrCreateConversation(users.user.id, users.receiver.id)
.then(conversation => socket.emit('priorMessages', conversation.messages));
});

Here, we’re calling the findOrCreateConversation class method we created earlier and then returning just the messages in that conversation to the client. (If there are no messages, Sequelize will return a null value, so we’ll need to handle that in the Native app.)

Lastly, we’re going to create an event listener to handle our messages:

socket.on('message', ({ text, sender, receiver }) => {
Message.createMessage(text, sender, receiver)
.then(message => {
socket.emit('incomingMessage', message);
const receiverSocketId = mobileSockets[receiver.id];
socket.to(receiverSocketId).emit('incomingMessage', message);
});
});

Here, we’ll use the createMessage class method we created and pass it the text content, sender object, and receiver object. We’ll then send the message object back to the user, where they’ll add it to their Redux store. We’ll also send the message to the recipient using Socket.io’s to method, which takes the ID of the socket we want to emit to and emits only to that socket.

And that’s the last of our event listeners! Here’s the entirety of that code:

Next up, we’ll create our React Native app! It’ll be a doozy, so hold on to your hats and stay tuned!