If you're aiming for high visibility of your blog content, then you have to post it on many channels like Dev.to, Hashnode, and Medium.
Not having a single source of truth for your content will cause a lot of manual work and soon your content becomes out of sync. Imagine how much time you could save if your blog post were automatically created and updated on multiple platforms.
Fortunately, there is a perfect solution. You can use Strapi as a single source of truth for your blog contents to automatically publish/update them on different platforms.
In this tutorial, we will explore how to do it by using Strapi and configuring webhooks in it. We also use Medium and Dev.to REST APIs, Hashnode GraphQL API, a serverless function that is hosted in AWS Lambda to handle the webhook call.
node <= 14.0.0
Please visit Node Previous Releases Page to download node v14 or earlier.npm >= 6.0.0
In this tutorial, we'll learn how a single source of truth can help with content creation. We start by creating a simple content type for our blog post. Then we develop a serverless function that gets triggered by Strapi webhooks. This serverless function receives blog post data from Strapi and by using Dev.to, Medium, and Hahnode API, creates a post in those platforms.
Content-Types Builder
For this article, our blog post has a title, tags, and content, but you may add more fields to your blog content type depending on your needs.
Generating an API key for Dev.to
Dev.to has a rest API to create, read, update, and delete posts. Authentication is required to create and update articles. You need to generate an API key first. To obtain one, please follow these steps:
In the "DEV API Keys" section, create a new key by adding a description and clicking on the "Generate API Key" button.
You'll see the newly generated key in the same view. Please save this API key in a safe place, We are going to use it in the next step.
When you make an API call to the Articles endpoint - https://dev.to/api/articles - you should include api-key
in the header like the following:
1 headers = {
2 'Content-Type': 'application/json',
3 'api-key': 'YOUR-API-KEY',
4 }
Like Dev.to, Medium also has REST API to create posts. In this tutorial, we are going to work with /posts
endpoint to create a new blog post.
Get the Authentication Token
To publish on behalf of a Medium account, you will need an access token. An access token grants limited access to a user’s account. To obtain one, please follow these steps:
Save this Authentication token in a safe place. You need this for the next step.
Get the user's ID
To create a post you need to make an HTTP post call to https://api.medium.com/v1/users/[user_id]/posts
as you see you need user id. To get the user id you need to make an HTTP GET call to https://api.medium.com/v1/me
. Run the following command in your terminal to get the user id.
1 curl https://api.medium.com/v1/me -H "Authorization: Bearer [YOUR-INTEGRATION-TOKEN]" -H "Content-Type: application/json"
This endpoint responds with your user information.
1 {
2 "data":{
3 "username":"YOUR-USERNAME",
4 "url":"https://medium.com/@your_user_id",
5 "imageUrl":"YOUR-USER-IMAHE",
6 "id":"YOU-USER-ID",
7 "name":"YOUR-USER-NAME"
8 }
9 }
The "id" is what you need to add in the endpoint URL to make an HTTP Post call.
Hashnode APIs are different. Unlike Dev.to and Medium, they provide a GraphQL API. If you are interested to learn more about their APIs, visit their API Playground and click on the Docs button on the right.
Generate API Token
Like the previous platforms, the first step to being able to use Hashnode API to create a new post is to get an authentication token. To get your token, follow these steps:
Please remember to save this token in a safe place. We need it for the next step.
Get your publicationId
To successfully create a post on Hashnode you need to provide the publicationId
in the GraphQL call. Go to your Hashnode blog dashboard.
In the URL in your browser between https://hashnode.com/
and /dashboard
you see a long alphanumeric value, https://hashnode.com/{This is the publication id}/dashboard
. That’s your publication id.
Ok, so now that we've set up our three platforms for successful API calls, let's talk more about the architecture of our solution and different pieces' responsibilities.
Remember, the idea behind our solution is that you don't have to go back and forth between various platforms. You can create or update your blog post in one place, which automatically creates or updates it across all the different platforms. Now let’s break down this diagram and talk about different pieces and their responsibilities.
As mentioned in the previous step, we are going to create a Serverless function to handle Webhooks call from Strapi. In this tutorial, we are using AWS Lambda but feel free to use your provider of choice.
Follow the instructions in AWS Lambda Developer Guide to create a Lambda Function
- My recommendation is to upload the code as a .zip
file to the Lambda dashboard because this code has some dependencies (requests
and gql
) that should be installed before execution. To learn more about this, visit AWS Guide for Deploying .zip File
- After creating the Lambda function successfully, In the function dashboard, Go to the Configuration tab → triggers and copy the API Gateway Endpoint. We use this URL for Strapi Webhooks.
Replace the function code with the following:
CHANGE-ME-*
with the actual value. For example CHANGE-ME-API-KEY
with your API key.1 import requests
2 import json
3 from gql import gql, Client
4 from gql.transport.requests import RequestsHTTPTransport
5
6
7 def devto_publisher(event, context):
8 # creating API call headers
9 headers = {
10 'Content-Type': 'application/json',
11 'api-key': 'CHANGE-ME-API-KEY',
12 }
13 # get the Strapi Webhook call data
14 body = json.loads(event["body"])
15 # Extract blog post title from Strapi webhook call
16 title = body\["entry"\]["title"]
17 # Extract blog content from Strapi webhook call
18 body_markdown =body\["entry"\]["Content"]
19 # Extract blog tags from Strapi webhook call
20 tags =body\["entry"\]["tags"]
21 # Create Dev.to article data sctruture
22 article = {
23 'title': title,
24 'body_markdown': body_markdown,
25 'published': True,
26 'tags': tags.split(',')
27 }
28 # create a data dictionary with article key and value
29 data = {
30 'article': article
31 }
32 # make post call to dev.to endpoint to create an article
33 response = requests.post(
34 'https://dev.to/api/articles', headers=headers, json=data)
35
36
37 def medium_publisher(event, context):
38 # Get User's ID from Medium
39 auth_headers = {
40 'Content-Type': 'application/json',
41 'Authorization': 'Bearer CHANGE-ME-YOUR-TOKEN',
42 }
43 auth_response = requests.get(
44 'https://api.medium.com/v1/me', headers=auth_headers
45 )
46 medium_id = auth_response.json()\['data'\]['id']
47 # Create Headers for making call to article creation endpoint
48 article_headers = {
49 'Content-Type': 'application/json',
50 'Authorization': 'Bearer CHANGE-ME-YOUR-TOKEN',
51 }
52 # get the Strapi Webhook call data
53 body = json.loads(event["body"])
54 # Extract blog post title from Strapi webhook call
55 title = body\["entry"\]["title"]
56 # Extract blog content from Strapi webhook call
57 body_markdown = body\["entry"\]["Content"]
58 # Extract blog tags from Strapi webhook call
59 tags = body\["entry"\]["tags"]
60 # Create blog data sctructure for Medium Endpoint
61 data = {
62 'title': title,
63 "contentFormat": "markdown",
64 'content': body_markdown,
65 'publishStatus': "public",
66 'tags': tags.split(',')
67 }
68 medium_endpoint = "https://api.medium.com/v1/users/{user_id}/posts".format(user_id = medium_id)
69 # Create article by making a call to Medium endpoint
70 response = requests.post(
71 medium_endpoint, headers=article_headers, json=data)
72
73
74 def hashnode_publisher(event, context):
75 # Create headers for GraphQL API Call
76 headers = {"Authorization": "CHANGE-ME-YOUR-TOKEN"}
77 _transport = RequestsHTTPTransport(
78 url="https://api.hashnode.com",
79 use_json=True,
80 headers=headers,
81 )
82 client = Client(
83 transport=_transport,
84 fetch_schema_from_transport=True,
85 )
86 # get the Strapi Webhook call data
87 body = json.loads(event["body"])
88 # Extract blog post title from Strapi webhook call
89 title = body\["entry"\]["title"]
90 # Extract blog content from Strapi webhook call
91 body_markdown = body\["entry"\]["Content"]
92 # Extract blog post tags from Strapi webhook call
93 tags = body\["entry"\]["tags"]
94 # Create graphql mutation query for creating a post
95 create_post_mutation = gql(
96 """
97 mutation ($input: CreateStoryInput!){
98 createPublicationStory(publicationId: "CHANGE-ME-YOUR-PUBLICATION-ID",input: $input){
99 message
100 post{
101 _id
102 title
103 }
104 }
105 }
106 """
107 )
108 # provide value for input variable
109 post_input = {
110 "input": {
111 "title": title,
112 "contentMarkdown": body_markdown,
113 "tags": [
114 {
115 "_id": "56744721958ef13879b94c7e",
116 "name": "General Programming",
117 "slug": "programming"
118 }
119 ]
120 }
121 }
122 # execute graphql call to Hashnode Endpoint
123 client.execute(
124 create_post_mutation,
125 variable_values=post_input
126 )
127
128 def lambda_handler(event, context):
129 devto_publisher(event, context)
130 medium_publisher(event, context)
131 hashnode_publisher(event, context)
132 return {
133 'statusCode': 200,
134 'body': json.dumps(event)
135 }
In this serverless function, we have 4 functions:
devto_publisher
that is in charge of creating a post in Dev.tomedium_publisher
that is in charge of creating a post in Mediumhashnode_publisher
that is in charge of creating a post in Hashnodelambda_handler
that is in charge of calling all the previous 3 functions, when your serverless function gets triggered.As I explained before webhooks send some data from one application to another application when a special event happens. In our case:
To make it clear the reason we want this Webhook is to send Published Blog post data from Strapi to our serverless function so our function can create it on different platforms. To create a new webhook login to your Strapi Instance, Click on Settings on the Left sidebar and pick Webhooks under the Global Settings.
Now click on the Add new webhook
button, Give your webhook a name like “Blog Publisher Endpoint” and in the URL section paste the API Gateway URL of your Lambda function. In the Events section, mark the Publish Event for Entry.
In this article, we are triggering a webhook only when we publish a blog in Strapi but in the real world, you need to trigger your webhook when you create, update, and delete a blog post.
In this short video, you will see how my implementation works. By creating a content type for blog posts, adding a webhook to trigger my AWS Lambda function, and publishing the post in Strapi automatically creates new articles on multiple platforms!
In this tutorial, you learned how to use Strapi as a single source of truth for your blog posts and automatically publish them to multiple platforms. This means that once an article is created in Strapi - it can be published on three different platforms: Dev.to, Hashnode, and Medium. If you are going to implement this as your publishing solution keep in mind in this article our blog content type is really simple but in the real world, you need to add more fields to it. You also need to be able to edit and delete posts. To do so you need to enable webhooks trigger for content deletion as well and in your serverless function, you need to implement a way to update existing posts and delete them via APIs.
I'm a full stack engineer who loves the challenges of working with cutting-edge technologies like React, Nextjs, Strapi.