Okay, I’m up, no need to yell at me. It’s day two - same morning routine, only this time I’ll skip breakfast ‘cause I got out of bed a little late. Don’t be like me though, never skip breakfast and always eat your god damn vegetables. As I promised, I started working on our little playlist so if you’ll excuse me, I think I’ll play it as I jump in the shower… just to let the groove get in - you know? Make sure you check it out down below, at the end of this post.
Quick reminder - I didn’t quit my 9-to-5 job. I’m just working from home until the COVID-19 outbreak ends. This being said I’ll kick this day off by taking care of some usual tasks. Right now I’m working on a React app that will allow users to monitor their career progress. All of our mentors will use it in order to steer youngsters down the right path, so I’m pretty glad I have the chance to work on it. Soon I’ll kickstart a new project though - a progressive web app powered by Angular so wish me luck! I guess I’ll need it. Later today I’m planning to wrap up the chat component of our React Native application, so you may want to stick around.
5 espressos & a croissant later
Before we get going, I think you should probably know that for today’s magic trick I’m going to use GraphQL. If you’re not familiar with it, make sure you look over its basic concepts before you continue reading. Don’t sweat! I bet that you’ll love it. You may even refuse to go back to your REST API afterwards, so proceed with caution. If you’ve already written your first queries & mutations though, I think we’re good to go.
To be able to code-along with me, you can set up a GraphQL server using Graphcool. You should be ready in no time. Just create an account and follow the steps presented in the documentation. I’m planning to write an in-depth tutorial that will help you create a GraphQL server from scratch, but till then you can safely use this BaaS alternative. After you’re done, let’s start building our schema. We’ll tackle the user first… after all, this is our main entity, isn’t it?
If you ever used a chat app before… like Facebook Messenger, WhatsApp, Skype or Telegram, you probably noticed that a user can access multiple chat rooms. These chat rooms are usually private, but can also be shared by multiple users. That’s why our user type will look like this:
type User @model {
createdAt: DateTime!
id: ID! @isUnique
name: String!
firstName: String!
profilePicture: String
email: String @isUnique
password: String
chatRooms: [ChatRoom!]! @relation(name: "UserChat")
}
As you can see, besides the generic fields, we added a collection of chat rooms which will contain all the conversations of our user. We also created a relation, that will work like a bridge between the User
type and the ChatRoom
type. We haven’t created the latter one just yet so let’s go.
type ChatRoom @model {
id: ID! @isUnique
participants: [User!]! @relation(name: "UserChat")
messages: [Message!]! @relation(name: "RoomChats")
}
It’s not rocket science. As you can see, we use the relation previously established to link the participants to a chat room. Moreover, we set up another one in order to create a meeting point between our chats and the messages sent by the users. This leads us to the third type we need to create - the Message
.
type Message @model {
id: ID! @isUnique
createdAt: DateTime!
message: String!
room: ChatRoom! @relation(name: "RoomChats")
from: User! @relation(name: "UserMessage")
}
Hold on, we’re almost done here and soon we’ll go back to our React Native app. We linked everything together, but as you may notice, there’s a slightly weird field in the Message
model. Yeah, I’m talking about the from
property. We should always know who sent a message, right? Otherwise, every conversation will be nothing more than a guessing game between the participants. Now I have to admit… that would be funny, and something I would totally do, but we really have to finish this app. A new relation appeared, right? This means we’ll have to go back to our User
type and fill in the gaps - relations should be mutual after all.
type User @model {
createdAt: DateTime!
id: ID! @isUnique
name: String!
firstName: String!
profilePicture: String
email: String @isUnique
password: String
chatRooms: [ChatRoom!]! @relation(name: "UserChat")
// Now we're done.
sentMessages: [Message!]! @relation(name: "UserMessage")
}
If you’re still here, good job and good luck ‘cause things are about to go crazy! Just init
your project if you haven’t already and update your package.json
file, by adding the following dependencies. If you don’t have an app yet, you can create one using Expo CLI or React Native CLI. You should probably go with Expo, especially if you’re a noob like me, but in case you’re looking forward to publishing your app soon, I totally recommend the second alternative. You can always eject from Expo though, so don’t stress.
"dependencies": {
"@expo/vector-icons": "^10.0.6",
"@react-native-community/masked-view": "^0.1.5",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.8",
"apollo-client-preset": "^1.0.8",
"apollo-link-http": "^1.5.16",
"apollo-link-ws": "^1.0.19",
"apollo-utilities": "^1.3.3",
"graphql": "^14.5.8",
"graphql-tag": "^2.10.1",
"react-apollo": "^3.1.3",
"subscriptions-transport-ws": "^0.9.16"
},
Before we write any Javascript code, let’s talk about subscriptions. Besides queries and mutations, the GraphQL ecosystem implements a third operation type which is usually used to push data from the server to the clients that are interested in specific events, like the creation of a new object. Because subscriptions specify a set of fields to be delivered to our apps, we may say that they are somehow similar to queries, but instead of immediately returning a single answer, they fetch a result every time a particular event happens on the server.
We need subscriptions because we’re interested in the new messages that arrive. Plus - we want to know exactly when they get in, so we could grab them and bring them to our users. Unfortunately, we’ll need to establish a socket connection to make this happen in real-time, so that’s the first thing I’m going to do. I’ll walk you through it, so don’t worry.
We will use Apollo to connect our app to the previously created GraphQL server, so it’s going to be a piece of cake. In the beginning, I’ll just import the WebSocketLink
from the apollo-link-ws
package and instantiate a new object as it follows.
import { WebSocketLink } from 'apollo-link-ws';
const wsLink = new WebSocketLink({
// PASTE YOUR URI HERE
uri: 'wss://subscriptions.graph.cool/v1/XXXXXXXXXXXXXXXXX',
options: {
reconnect: true
}
});
I know it looks confusing, but trust me, it’s a no-brainer. To get things going, you’ll need to go to the Graphcool console and find the URI for your Web Socket connection. Simply press the Endpoints
button in the left down corner of the side menu and select the Subscriptions
tab. Copy and paste the given link into the code as I indicated and you’re done. Well not really done, because we still need a way to send our queries and mutations to the server. These are using the HTTP protocol though, so let’s set up another link:
import { HttpLink } from 'apollo-link-http';
const httpLink = new HttpLink({ uri: 'https://api.graph.cool/simple/v1/XXXXXXXXXXXXXXXXX' });
That’s right, you guessed it. You’ll need to copy and paste the URI for your HTTP endpoint as well. You’ll find it in the same place, but this time make sure to select the Simple
tab to get. Cool! Not how do we know when to use one or the other? Easy peasy lemon squeezy:
import { split } from 'apollo-client-preset';
import { getMainDefinition } from 'apollo-utilities';
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink,
);
All I did here was to combine those two links we created a couple of minutes ago into a single one. To make this happen I used the split
operator to make sure that our subscriptions go through the web socket connection, while all the other operations are sent via HTTP. Now it’s the time to instantiate our Apollo client and provide it to our application using the ApolloProvider
. If I do just that and then I put everything together my App.js
file will look something like this:
import React, { Component } from 'react';
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import { Root, store, persistor } from './src/navigators/AppNavigator';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { split } from 'apollo-client-preset';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { PersistGate } from 'redux-persist/integration/react';
StatusBar.setBarStyle('light-content', true);
const wsLink = new WebSocketLink({
uri: 'wss://subscriptions.graph.cool/v1/ck5s2qiz960t60160cvl3jb2n',
options: {
reconnect: true
}
});
const httpLink = new HttpLink({ uri: 'https://api.graph.cool/simple/v1/ck5s2qiz960t60160cvl3jb2n' });
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink,
)
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
class App extends Component {
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ApolloProvider client={client}>
<Root />
</ApolloProvider>
</PersistGate>
</Provider>
);
}
}
AppRegistry.registerComponent('App', () => App);
export default App;
As you can see, my Redux store is already in place and ready to persist data. If you wonder how I did that, you must have missed the first chapter of this diary, but don’t sweat, bro - you can find it here.
We’re ready to get the chat up and running now, but this post is getting kinda long though, so I decided to divide our tutorial into multiple parts. To be one step ahead, I would like to prepare our battlefield for the next episode of Quarantine Days. To do that I’m gonna create a new folder in my src
directory and name it graphql
. Here we’re going to keep all of our operations. Let’s start with the queries. First, we might need some data about the current user so create a queries.js
file and follow my lead:
import gql from 'graphql-tag';
const GET_USER_QUERY = gql`
query getUserData($userEmail: String, $userID: ID) {
User(email: $userEmail, id: $userID) {
id,
email,
name,
firstName,
profilePicture,
}
}
`
export {
GET_USER_QUERY,
}
At the moment, I trigger this operation right after a user logs in, and then I save the data returned by it in my Redux store. You can see that in order to make this work, we must provide the query with the user email and id. Those are in my case coming from the server as a JWT when the login process is successful. What I would like to evidentiate now is the fact that the expected response is minimal, because I need to persist these pieces of information. There’s no point in asking for the user’s chat rooms now if he didn’t even reach the Chat screen. Moreover, I don’t want to persevere any data about his conversation in my store ‘cause most of the time it’s useless and will occupy a lot of space.
After we did this, we can go ahead and prepare the query responsible for bringing the chat rooms.
const GET_USER_CHATS = gql`
query getUserChats($userID: ID) {
allChatRooms(filter: {
participants_some: {
id: $userID
}
}) {
id
participants(filter: {
id_not: $userID
}) {
id
profilePicture
name
firstName
}
}
}
`
Amazing! Let’s make some observations. The first one is that our Graphcool server doesn’t offer support for a query that gets all the chat rooms of a specific user. That’s why we use the allChatRooms
query together with the filter` option - to specify that we only need the chats that are of interest to our guy. Secondly, we want to avoid fetching useless data so we filter the retrieved participants as well. We already have the id, the name and the profile picture of the current user, so why would we pull it from the server again?
In the end, let’s create a query that retrieves the messages of a chat room, by applying the same filter trick.
const GET_MESSAGES_QUERY = gql`
query getMessages($roomID: ID) {
allMessages(filter: {
room: {
id: $roomID
}
}) {
id
message
from {
id
firstName
name
}
}
}
`
Now we’ll be able to retrieve information about the user, his chats as well as the messages he sent or received. Something is missing though… We should also have the possibility to create new messages via mutations, so let’s set up a new file called mutations.js
and prepare that for tomorrow.
import gql from 'graphql-tag';
const CREATE_MESSAGE_MUTATION = gql`
mutation createMessage($message: String!, $fromId: ID!, $roomID: ID) {
createMessage(
message: $message,
fromId: $fromId
roomId: $roomId
) {
id
message
from {
id
}
}
}
`
export {
SIGN_IN_MUTATION
}
Perfect. I just hope you’re not bored to death yet. As you can see we need 3 variables here - the message(obviously), the id of the user sending this message(in our case the current user) and the id of the room he’s in. You’ve probably remarked that mutations are similar to a queries ‘cause only the root type is different. Told you - piece of cake.
By now I think you got a good grip on how these things go, if you didn’t I’ll let you do some experiments on your own and tomorrow we’ll tackle our first subscriptions. Feel free to shoot me an email if you get lost, and I’ll make sure to get in contact with you as soon as possible.
Until we meet again, I’ll drop my playlist in here, as I promised, to make your coding session more pleasant.
See you soon.
Subscribe to our newsletter and stay updated.