LightChat, a Slack.com clone, is an application giving users the possibility to communicate through posting on channels or sending direct messages.
Check out the wiki for development details!
git clone https://github.com/valery-nguyen/lightchat.git
cd lightchat
npm install
cd client
npm install
- Set up the MONGO_URI and secretOrKey
// config/keys_dev.js
module.exports = {
MONGO_URI: 'mongodb+srv://...',
secretOrKey: '...'
};
- Start the server and client concurrently
npm run dev
heroku container:login
heroku container:push web --recursive -a lightchat-app
heroku container:release web -a lightchat-app
- Server side: setting up a HTTP server to serve GraphQL queries and mutations, as well as a WebSocket server to handle subscriptions, both servers listening on one unique port
// server/server.js
const express = require("express");
const { createServer } = require("http");
const { SubscriptionServer } = require("subscriptions-transport-ws");
const app = express();
app.use(
"/graphql",
expressGraphQL(req => {
return {
schema,
context: {
token: req.headers.authorization
},
graphiql: true
};
})
);
const ws = createServer(app);
SubscriptionServer.create({
execute,
subscribe,
schema,
}, {
server: ws,
path: "/",
});
const port = process.env.PORT || 5000;
ws.listen(port, () => {
console.log(`Server listening on port ${port}`);
console.log(`WebSocket listening on port ${port}`);
});
- Client side: setting up the HTTP and WebSocket links
// client/src/index.js
let uri;
if (process.env.NODE_ENV === "production") {
uri = "/graphql";
} else {
uri = "http://localhost:5000/graphql";
}
const httpLink = createHttpLink({
uri,
headers: {
authorization: localStorage.getItem("auth-token") || ""
}
});
let wsUri;
if (process.env.NODE_ENV === "production") {
wsUri = "wss://" + window.location.host + "/";
} else {
wsUri = "ws://localhost:5000/";
}
const wsLink = new WebSocketLink({
uri: wsUri,
options: {
reconnect: true,
connectionParams: {
authorization: localStorage.getItem("auth-token") || ""
},
}
});
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);
- Server side: subscriptions implementation
Pubsub:
// server/schema/pubsub.js
const { PubSub } = require("apollo-server");
const pubsub = new PubSub();
module.exports = pubsub;
Schema:
// server/schema/schema.js
module.exports = new GraphQLSchema({
query,
mutation,
subscription
});
Subscriptions:
// server/schema/subscriptions.js
const subscription = new GraphQLObjectType({
name: "Subscription",
fields: () => ({ messageSent, ... })
});
const messageSent = {
type: MessageType,
resolve(data) {
return data.messageSent;
},
subscribe: withFilter(
() => pubsub.asyncIterator(["MESSAGE_SENT"]),
(payload, variables) => {
return true;
}
)
};
Publish:
// server/schema/services/messages.js
const addMessage = async (data, context) => {
...
await pubsub.publish("MESSAGE_SENT", { messageSent: message, channel: channel});
...
};
- Client side: subscriptions implementation
Subscriptions:
// client/src/graphql/subscriptions.js
export default {
NEW_MESSAGE_SUBSCRIPTION: gql`
subscription onMessageSent {
messageSent {
_id
user_id
body
date
channel
author
}
}
`,
...
}
React component:
// client/src/components/messages/main_chat.js
<Subscription subscription={NEW_MESSAGE_SUBSCRIPTION}>
{({ data, loading }) => {
...
}
}
</Subscription>
- Valery Nguyen
- The starter group project can be found here, which was developed in collaboration with Chris Meurer and Colin Reitman.