Tutorial - Creating an Email Microservice with Typescript and Dapr
Once in a while a new project comes out that I am totally hyped about! Dapr is such a Project! In this article I would like to go in-depth on how we can quickly (< 10min!!!) create a Microservice that will send an email through a Microservice invocation mechanism!
Dapr Introduction
In my own words, Dapr takes away the burden of integrating all these pesky APIs and allows you to focus on what counts: your business logic. Next to that, Dapr also implements building blocks for Service-to-Service Invocation, State Management, Publish and Subscribe, Actors, Observability, Secret Management all while being extensible!
What will we create?
In this article I want to create an Email Microservice which should be able to do the following:
- We should be able to call a
send
endpoint from another microservice - We should be able to provide
subject
andemailTo
email - I want to utilize Twilio Sendgrid
💡 I am utilizing Sendgrid, but Dapr allows us to instantly swap it out for any other supported binding as described in the documentation..
Bootstrapping our project
For this project I will be utilizing Typescript
, since this is not the scope of the article to set this up, I recommend reading my previous article - bootstrapping a Typescript Project on how you can set this up yourself in a few commands.
Creating an Email Microservice
Once our initial Typescript project is bootstrapped, add a components
folder as well for our Dapr components to be stored in. The final directory structure will then look like this:
.
├── README.md
├── components # Holds all our bindings for Dapr
├── dist # Holds the compiled code
├── node_modules
├── package-lock.json
├── package.json
├── src # Holds the source code
└── tsconfig.json
Initializing our Dapr Component
In the components
folder we just created we keep all the .yaml
descriptions for Dapr to know how to configure its components. For sendgrid we create the following file:
components/binding-sendgrid.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: binding-sendgrid # the name of the component
namespace: default
spec:
type: bindings.twilio.sendgrid
version: v1
metadata:
- name: emailFrom
value: "info@youremail.com" # optional
- name: apiKey
value: "<YOUR_SENDGRID_KEY>" # required, this is your SendGrid key
Creating the Email Service
Once that is done, we need to create the email service itself. The way this works is:
- We install the Javascript SDK (
npm i -S @roadwork/dapr-js-sdk
) - We initialize the Javascript SDK (
let client = new Dapr(...)
) - We listen on incoming requests from the Dapr Invoker (
client.invoker.listen(...)
)
Therefore, we put the following in our index.ts
file:
src/index.ts
import Dapr, { InvokerListenOptionsMethod, Req, Res } from "@roadwork/dapr-js-sdk";
// Dapr is running completely internal, so we connect to 127.0.0.1:3500
const client = new Dapr("127.0.0.1", 3500);
// Implements the onSend method for when the Dapr invocation happened
async function onSend(req: Req, res: Res) {
const subject = req.body?.subject;
const emailTo = req.body?.emailTo;
if (!subject || !emailTo) {
throw new Error(JSON.stringify({
error: "Missing subject or emailTo fields"
}));
}
await client.binding.send("binding-sendgrid", "<html><body>Hello World</body></html>", {
emailTo,
subject
});
console.log("Done Sending Email")
return res.json({
isDone: true
})
}
// Initialize the SDK to listen on the POST /send endpoint
// Note: in the SDK this spins up a server that listens on a port. This port is defined through the DAPR_APP_PORT environment variable, so we need to open it when starting Dapr!
async function main() {
client.invoker.listen("send", onSend, { method: InvokerListenOptionsMethod.POST })
}
main()
.catch((e) => {
console.error(e);
process.exit();
})
Now this is done, we can start our application utilizing the command below. This command might seem complex, but it's actually quite straightforward:
- We start our application
npm run start:dev
- We ensure that Dapr waits on our application listening on its internal port, which is set in the SDK by the environment variable
DAPR_APP_PORT
so we configure this and set the Dapr--app-port 3001
as well - We want Dapr running on port 3500 for debugging purposes (we can send local requests to http://127.0.0.1:3500 as the dapr endpoint)
- We define our components path as
./components/
where Dapr will look for the bindings (or other components such as PubSub)
DAPR_APP_PORT=3001 dapr run --app-id msvc-email --app-port 3001 --dapr-http-port 3500 --components-path ./components/ npm run start:dev
Testing the Email Microservice
Everything is in place now to run the code, but we should still create some piece of code to quickly test this. Well, wrong! In normal SDKs we would have to do this, but Dapr actually implemented that with the CLI!!
We thus install the Dapr CLI (see documentation) which we initialize with dapr init
💡 This initializes Dapr self-hosted on our container environment
Once that is done we can utilize the internal invoke
command to trigger our application (a dream come through for testing 😉) so run the command below to trigger this:
dapr invoke --app-id msvc-email --method send --data '{"subject": "my-subject", "emailTo": "info@your-email.com"}'
Which will kick of the Sendgrid binding and send an email towards your inbox!
Conclusion
So what seemed complex actually wasn't! In just 40 lines of code (which we can simplify to ~3 if we are not counting validation, …) we created a Microservice that can be called and implements an email sending service. Next to that we have the added benefits that scale-out is trivial, monitoring is out of the box, SDKs can be utilized for the invocation and most importantly service discovery is done for us! Thus, not requiring a central repository.