Previous page: Setup GraphQL with Apollo Server
GraphQL Queries
Retrieve a list of messages from Slack to build a GraphQL query
We will create the implementation for our GraphQL messages query to enabled the application to retrieve a list of messages from Slack.
From the web-api directory of your project folder, create the following folder structure:
|--api
| |--slack-integration
| | |--data-mappings
| | |--data-sources
| | |--resolvers
Alternatively open your terminal or command-prompt and run the following commands to create the same folder structure:
cd <project-folder>/web-api/api
mkdir -p ./api/slack-integration
cd ./api/slack-integration
mkdir ./data-mappings
mkdir ./data-sources
mkdir ./resolvers
Then create a file named messages.messages.query.js
in the new resolvers
folder and open it in your favourite IDE.
cd ./resolvers
touch messages.messages.query.js
In this file we will start by defining an asynchronous function for the messages query of our GraphQL schema.
module.exports = async function messages (parent, args, { dataSources }) {
}
GraphQL query resolvers
all take four parameters. A parent
parameter is an object containing the
result of the parent resolvers
postcondition
as composed in the GraphQL schema. An args
parameter that holds all
variables the query was called with. A context argument to store supplemental
data defining the context in which the query was called. This is where you
can store things like user information for each query. Here we are
destructuring
the context object to use dataSources
as a variable in our new
function. There is also a fourth parameter that stores information about the
execution state but we won't need this here.
Message conversations
The Slack API has the concept of channels
, which fits closely to our world
of message conversations. We will need to get the correct conversation
associated with the current user before displaying the list of messages. For
now we are going to hard-code a channel name that we will always use.
We will also need the Slack client for Node, which has been conveniently installed with the boilerplate project to interact with the Slack API and retrieve conversation data.
We will need to create a new instance of the WebClient
with our Slack web
token from the environment variables that we
previously setup
to succesfully communicate and access Slack API data. And create three
functions above our messages
function to get the conversation and message
history from Slack.
const { WebClient } = require('@slack/client');
const webAPI = new WebClient(process.env.SLACK_WEB_ACCESS_TOKEN);
/**
* Get the complete list of conversations associated with your Slack web token
*/
function getConversationsList() {
return webAPI.conversations.list();
}
/**
* Get the Slack channel we want by 'name' to get associated data such as the
* channel id
*/
function getConversation(name) {
return getConversationsList()
.then(res => res.channels.find(channel => channel.name === name));
}
/**
* Get the Slack channel history of messages and limit the amount by the 'max'
* value
*/
function getConversationsHistory({ id: channelId, max = 10 }) {
return webAPI.conversations.history({ channel: channelId, limit: max })
.then(res => res.messages);
}
module.exports = async function messages (parent, args, { dataSources }) {}
At the bottom of the file, in our messages
function we await
the
promise
returned from Slack to resolve and get the list of messages for the
conversation.
//Existing code...
module.exports = async function messages (parent, args, { dataSources }) {
const channelName = 'conversation-1';
const conversation = await getConversation(channelName);
}
But wait, what if we haven't created a conversation with our new channel name yet?
In order to get a list of messages from Slack, we have to get an
identifier for the conversation by using our unique name conversation-1
to
select the id from the list of conversations. Then with this identifier, we can
retrieve a list of messages from the history
endpoint associated with the
conversation.
If a conversation doesn't exist, we don't want to attempt to get messages, instead we can instantly return an empty array of messages. If a conversation does exist, we can return the messages for the conversation.
//Existing code...
module.exports = async function messages (parent, args, { dataSources }) {
const channelName = 'conversation-1';
const conversation = await getConversation(channelName);
return (!conversation
? Promise.resolve([])
: getConversationsHistory({
id: conversation.id,
max: args.max
}));
}
We now safely return a list for a conversation whether it has been created or
not but there is another problem with our code. The
message model from
Slack's API doesn't quite fit the model for a Message
defined in our schema.
We will need to map the model from Slack to our custom model. We will do this
by defining a new function before our messages
function and use a Message
class for storing all message data in our model.
class Message {
constructor(id, userId, text, { timestamp = null, fromYou = false, user = null } = null) {
this.id = id;
this.userId = userId;
this.text = text;
this.timestamp = timestamp;
this.fromYou = fromYou;
this.user = user;
}
}
function slackMessageMap(messageData, { user } = {}) {
return new Message(
messageData.ts,
messageData.user,
messageData.text,
{
fromYou: !!messageData.bot_id,
/**
* Slack appends an ordering integer at the end of the timestamp that
* we need to remove to correctly format the message time.
*/
timestamp: messageData.ts.split('.')[0],
user
}
);
}
module.exports = async function messages (parent, args, { dataSources }) {
//...
}
Going back to our messages
function at the bottom of the file, we can now
use our new mapping function to create the correct model for a message used in
our schema by using the .then
method returned from our promise to transform
the result.
module.exports = async function messages (parent, args, { dataSources }) {
//...
return (!conversation
? Promise.resolve([])
: getConversationsHistory({
id: conversation.id,
max: args.max
}))
.then(messages => messages.map(message => slackMessageMap(message)));
}
All we have to do now is add our messages query resolver to the
web-api/index.js
file. Import the messages.messages.query.js
file at the
top of the file:
//...
const { messageGQL } = require('./api/messages/message.model');
const { userGQL } = require('./api/users/user.model');
/**
* Import our new messages resolver
*/
const messages = require('./api/slack-integration/resolvers/messages.messages.query');
const typeDefs = gql`
#...
`;
//...
Then add the Query
property with messages
resolver to the Apollo server
instance:
//...
const server = new ApolloServer({
typeDefs,
resolvers: {
/**
* New Query and messages resolver
*/
Query: {
messages
}
},
//...
});
//...