Sending Notifications
Let's create a module to send emails to announce a new concert. We will keep things simple by printing the email to the console instead of using an email server. Later, when we dive deeper into Deno support, we will integrate with an email server using npm package.
We will use TypeScript to define the module (the other choice would be JavaScript).
The module will expose a single function, sendNotification
, which takes a single argument, concertId
, which is the concert we want to notify. The implementation will need to query the database to get the concert details and the email addresses (from the Subscriber
model). Effectively, we want the implementation to have access to the queries. We make this possible by adding another argument @inject exograph: Exograph
. The Exograph runtime will supply the exograph
object through which you can execute queries. Note that the mutation exposed through GraphQL will still have only one argument--concertId
. In other words, the injected arguments are not exposed through the GraphQL mutation.
Note the @access
annotation on the mutation. We want to restrict access to this mutation to only users with the "admin" role. We can do that by using AuthContext
defined earlier.
@deno("notification.ts")
module NotificationService {
@access(AuthContext.role == "admin")
mutation sendNotification(concertId: Int, @inject exograph: Exograph): Boolean
}
Before implementing the module, let's create the Subscriber
model.
@postgres
module ConcertData {
...
@access(AuthContext.role == "admin")
type Subscriber {
@pk id: Int = autoIncrement()
email: String
subscribed: Boolean
}
}
Here, for simplicity, we restrict access to the Subscriber
model to only users with the "admin" role. In an actual application, you would implement a subscription flow with a confirmation email.
With exo yolo
watching, you see a new file notification.ts
generated for you (it is just a starter code; you can also manually create it). Open that file and replace it with the following (essentially, we are supplying the function body):
import type { Exograph } from "./exograph";
export async function sendNotification(
concertId: number,
exograph: Exograph
): Promise<boolean> {
const concertOperation = await exograph.executeQuery(
`query($concertId: Int!) {
concert(id: $concertId) {
title
}
}`,
{
concertId: concertId,
}
);
const subscribersOperation = await exograph.executeQuery(
`{
subscribers(where: {subscribed: {eq: true}}) {
email
}
}`
);
const concertTitle = concertOperation.concert.title;
const subscriberEmails = subscribersOperation.subscribers.map(
(subscriber) => subscriber.email
);
const emailBody = `
<h1>${concertTitle}</h1>
<p>You have been invited to the concert!</p>
`;
return await sendEmail(subscriberEmails, "Concert Announcement", emailBody);
}
async function sendEmail(
to: string[],
subject: string,
body: string
): Promise<boolean> {
console.log(
`Sending email
to: ${to.join(", ")},
subject: ${subject},
body: ${body}`
);
return true;
}
We execute two queries in the module: one to get the concert details and one to get the list of subscribers. Then, we compose the email body and send the email to the obtained addresses.
A quick recap:
- The mutation's access rule requires the user to have the "admin" role.
- The access rule for the
Subscriber
type also requires that the user has the "admin" role. - The Exograph runtime injects the
exograph
object to thesendNotification
function. This object exposes theexecuteQuery
method, which the implementation may use to execute queries. TheexecuteQuery
method returns a promise that resolves to the query result. - The
executeQuery
method takes two arguments: the GraphQL query and the variables. The variables are optional. In this case, we pass theconcertId
as a variable for the first query and leave it empty for the second query. - Even in the TypeScript code, all queries and mutations still follow the same access control rules. In this case, since the invoker of the mutation must be an admin, the queries will also be executed as an admin. If you need to bend the rule, Exograph allows you to do so in a principled way. See the ExographPriv section for more details.
Add a couple of subscribers to the database through our GraphiQL interface.
mutation {
createSubscribers(
data: [
{ email: "[email protected]", subscribed: true }
{ email: "[email protected]", subscribed: false }
{ email: "[email protected]", subscribed: true }
]
) {
id
}
}
And now send the email for the concert with id 1.
mutation {
sendNotification(concertId: 1)
}
In the console where exo dev
is watching, you will see the following:
Sending email
to: [email protected], [email protected],
subject: Concert Announcement,
body:
<h1>An evening vocal concert</h1>
<p>You have been invited to the concert!</p>
Note that since we filtered the subscribers to only those who are subscribed, we only sent the email to those. Specifically, '[email protected]' has not subscribed, so it did not receive the email.