Previous page: Use a GraphQL query to populate a list of messages in an Angular component
GraphQL Mutations
Create and POST a new message using a GraphQL Mutation
We have our GraphQL query to retrieve a list of messages for our Slack hosted conversation. And we have the Angular components we need to display the list of messages once we've retrieved the data. Lets take a look at how to create a message in our conversation from our application using Apollo-Angular and GraphQL mutations.
Prerequisites
Before we move on to developing the new functionality, we need to set two more environment variables for our GraphQL API, of which we will get from Slack.
If you're not already signed in to Slack, go ahead and sign in to your new workspace, the URL should be in your inbox, created in the setup guide.
If you previously created a channel in Slack called conversation-1
and don't
have a first message showing in your Angular app that indicates that you have
joined the conversation, you'll need to delete the channel by logging in to
your workspace at https://slack.com/signin,
clicking on the channel conversation-1
, then opening the gear icon menu
towards the top of the page, selecting 'Additional options' and choosing
'Delete this channel' (towards the bottom of the page).
Create the new channel conversation-1
(if you haven't created this before,
you can easily add a new channel from the left-hand navigation), then open the
gear icon menu and click Add app
. Find the app you previously created in the
setup guide and add it to the channel. Slack should notify you that the app has
joined the channel conversation-1
.
At this point, you should have one user (yourself) and one app added to the new channel with no personal messages.
If we open our new Angular app and refresh the page, you should see a message that indicates you've joined the channel, something like '<@URN2FAKE> has joined the channel'. And another to indicate the app has joined the channel.
You'll need to copy the id defined inside the tags <@...>
associated with your
user name (in the case above, this would be URN2FAKE
) and set this as the
SLACK_ME_ID
environment variable for your GraphQL API. You can set this
from the terminal or command prompt that you're running the API from:
export SLACK_ME_ID=Replace with your id here
Then copy the app id associated with your new app from the second 'joined'
message and similar to how we did above, set this as the environment variable
SLACK_APP_ID
.
export SLACK_APP_ID=Replace with app id here
Create the schema Mutation
We're first going to define a top-level mutation in our GraphQL schema. From
your project folder, open the file <project folder>/web-api/index.js
and
add the following code to the typeDefs
variable in the middle of the file:
//Existing code...
const typeDefs = gql`
type Query {
#Existing code...
}
# Add the top level mutation
type Mutation {
createMessage(text: String): Message
}
#Existing code...
`;
This will define a mutation in our schema called createMessage
that takes a
text
string parameter. This will hold the contents of a message sent by an
anonymous user, which we'll use to send to Slack.
Extending the conversations data source
Before we create our resolver, we will need to extend our conversations API data source that we'll need in our new resolver. We will need to add three new methods for interacting with the Slack API to start a conversation.
Open the file api/slack-integration/data-sources/conversations.js
and at
the end of the class, add the createConversation
method:
// Existing code...
module.exports = class ConversationsAPI extends DataSource {
// Existing code...
/**
* Create a Slack channel
*/
createConversation(name, userIds) { // <-- New method
const { webAPI } = connection.getConnection();
return webAPI.conversations.create({ name, user_ids: userIds })
.then(res => res.channel);
}
}
The createConversation
method will create a Slack channel named with the
value name
and add users to the channel from the array of userIds
.
The next method we will create will be for inviting users to a channel. Although order is not significant, add this after the last method.
module.exports = class ConversationsAPI extends DataSource {
// Existing code...
createConversation(name, userIds) {
// Existing code...
}
/**
* Invite users to a Slack channel
*/
inviteUser({ channelId, userIds }) { // <-- New method
const { webAPI } = connection.getConnection();
return webAPI.conversations.invite({ channel: channelId, users: userIds.join(',') })
.then(res => res.channel);
}
}
This method takes a channelId
and list of user ids that are used to join the
channel.
We have our methods for creating a conversation channel and inviting users
to the channel. The last method we will add to our DataSource
will be used to
actually post a message to the new channel. We can add this to the end of our
class:
module.exports = class ConversationsAPI extends DataSource {
// Existing code...
createConversation(name, userIds) {
// Existing code...
}
inviteUser({ channelId, userIds }) {
// Existing code...
}
/**
* Create a message in a channel
*/
chatPostMessage(channelId, text) { // <-- New method
const { webAPI } = connection.getConnection();
return webAPI.chat.postMessage({ channel: channelId, text })
.then(res => res.message);
}
}
Again, we are using the shared Slack connection to call chat.postMessage
with a channel id and text option then
return the message
from the
response. The webAPI
methods all return asynchronous Promises.
With the ability to create a message in a new Slack channel, we are all set to now integrate our resolver function.
Create the resolver
Just like GraphQL queries, we need to create a resolver to handle the new incoming request.
In your terminal or command prompt, change directory to the web-api
folder
within your project folder then create the file
api/slack-integration/resolvers/messages.createMessage.mutation.js
and open
it.
touch ./api/slack-integration/resolvers/messages.createMessage.mutation.js
At the top of the file we need to import the slackMessageMap
function we
previously used for our query and then create a new function definition that
we'll export as our GraphQL resolver.
const slackMessageMap = require('../../slack-integration/data-mappings/message');
module.exports = async function createMessage (parent, args, { dataSources, session }) {
};
The async
keyword will allow us to use the await
keyword in the function
body. This will pause execution of the function to resolve a promise and get
the result before moving on to execute the next line of code. We can do this
by adding the next two lines.
const slackMessageMap = require('../../slack-integration/data-mappings/message');
module.exports = async function createMessage (parent, args, { dataSources, session }) {
const channelName = 'conversation-1';
const conversation = await dataSources.conversationsAPI.getConversation(channelName);
};
We first need to check if our conversation exists before we create a message.
We will also create another async
method that we will conditionally call to
create a new conversation if one doesn't exist.
const slackMessageMap = require('../../slack-integration/data-mappings/message');
module.exports = async function createMessage (parent, args, { dataSources, session }) {
const channelName = 'conversation-1';
const conversation = await dataSources.conversationsAPI.getConversation(channelName);
async function createConversation() { // <-- New async method
return dataSources.conversationsAPI.createConversation(channelName, [process.env.SLACK_ME_ID])
.then(channel => dataSources.conversationsAPI.inviteUser({
channelId: channel.id,
userIds: [process.env.SLACK_ME_ID, process.env.SLACK_APP_ID]
}));
}
};
In the new method, we call our createConversation
and inviteUser
methods to
first create a conversation, then invite the relevent users to the
conversation, including your new Slack app using the environment variables we
previously set (process.env.SLACK_APP_ID
).
We can now add the code that will conditionally call these methods if a
conversation doesn't exist to first create the channel and id or use the
existing found channel id needed to post a new message. After the message has
been created, we return a mapped version of the message data to the client
using the function slackMessageMap
.
const slackMessageMap = require('../../slack-integration/data-mappings/message');
module.exports = async function createMessage (parent, args, { dataSources, session }) {
const channelName = 'conversation-1';
const conversation = await dataSources.conversationsAPI.getConversation(channelName);
async function createConversation() {
// Existing code...
}
// New return value
return (conversation ? Promise.resolve(conversation) : createConversation())
.then(channel => dataSources.conversationsAPI.chatPostMessage(channel.id, args.text))
.then(slackMessageMap);
};
This wraps up creating our GraphQL mutation to create a message. Finally, we have to register our new resolver to the exported object that will get included in the Apollo Server resolvers.
Open the file api/slack-integration/resolvers/messages.resolver.js
. We need
to import our new mutation resolver and declare it in our exports object.
const messages = require('./messages.messages.query');
const Message = require('./message');
const createMessage = require('./messages.createMessage.mutation'); // <-- New import
module.exports = {
Query: {
messages
},
Mutation: { // <-- New property to add
createMessage
},
Message
};