Connecting Strapi with GitHub to regenerate my website

Strapi is a great open source headless CMS. Therefore, websites are not directly connected to it. But how can I get my content continuously from Strapi to my website? Today I want to share my thoughts with you and show you the way I made it work for me.

Initial thoughts

Working with a headless CMS is also a paradigm shift. All content is created in a separate system and the website is generated statically based on the created pages. Every time, I trigger a new build of my website, it fetches all data from Strapi before being deployed on the web server. The code of my website is stored on GitHub, so I can use GitHub Actions to continuously build and deploy it. To react on content changes, I can trigger the repository_dispatch event via webhooks (see https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event). Strapi offers the possibility to create webhooks, too. What could possibly go wrong?

The problem

As it turned out, it’s not that straight-forward as I initially thought. GitHub expects to get a request in a specific format. The API requires specific header values and a body containing the event in event_type. Strapi sends the events in the body too, but calls the property event. You can set the header values, but the request body cannot be customized. First, I thought about transforming the request with a middleware, but I couldn’t figure out how to do it. My co-worker brought me to the idea to create a custom serverless function to receive the Strapi webhook request and transforming it to the request I need to trigger the GitHub dispatch event.

Creating the function

For this case, I used Azure Functions to get the job done. They can be written in C# (a language I’m very comfortable with) and are executed in the Azure cloud, where my website is already hosted.

To create a function to which Strapi can communicate to, I needed an HTTP trigger the function listens on. The code looks as follows:

    [FunctionName("CallGitHubAction")]
    public static async Task<IActionResult> RunAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
    {
        log.LogInformation("Retrieved request to trigger GitHub action.");

        using (var httpClient = new HttpClient())
        {
            httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json");
            httpClient.DefaultRequestHeaders.Add("Authorization", $"token {Environment.GetEnvironmentVariable("GitHubToken")}");
            httpClient.DefaultRequestHeaders.Add("User-Agent", "azure-function-app");

            const string url = "https://api.github.com/repos/{username}/{repo}/dispatches";

            var body = new StringContent(@"{""event_type"": ""strapi_update""}", Encoding.ASCII, "application/json");

            var httpResponse = await httpClient.PostAsync(url, body);
            if (!httpResponse.IsSuccessStatusCode)
            {
                return new StatusCodeResult((int)httpResponse.StatusCode);
            }
        }

        return new OkResult();

    }

There are some things to have a look on. After receiving an HTTP POST request from Strapi, it creates an HTTP client and adds the required header values to it. GitHub requires a user agent. If you don’t define the header value for it, the API rejects your request with the status code 403. Then, the endpoint to the desired repository dispatcher is defined. The body is a simple string content containing the key “event_type” and its value “strapi_update” that could be used to filter repository dispatch events in the GitHub Action. After sending the HTTP request, it checks the returned status code and responds to Strapi whether the request was successfully transformed or not.

Deployment and Integration

Now, the function is ready to be deployed. Therefore, a new function app in the Azure portal must be created. The process is pretty much the same as creating a Web App. You have to choose a resource group and define a Function App name. This time, I decided to deploy the code directly in it, so I decided for “Code” as “Publish” option. As runtime stack, I used .NET 6. With .NET 6 it also doesn’t matter if your app runs on Windows or on Linux. But with Linux as OS, you can save some money. For me, it was especially practical, because I already have a Linux App service plan which I can reuse. Using a dedicated App Service plan allows me to use unused resources and enables to predict costs. The downside is, that the function app’s scalability is limited by the size of the App Service plan. In this case, it’s fine, because I only use the function to trigger deployments when I update my content. In the hosting section, I selected my existing storage account. Don’t forget to provide your GitHub personal access token in the environment variable “GitHubToken” under Settings > Configuration > Application settings. To create a PAT, see the GitHub Docs. Your token only needs the whole repo scope.

As you might already have noticed, the HTTP trigger in the function was defined with the authorization level of “function”. This means that you have to provide some kind of credential to authorize your request. The defined authorization level means that a function specific API key is required. I’d recommend creating a new function key by accessing {your function app} > Functions > {your function (CallGitHubAction in my case)} > Function Keys. To create a new key, you just have to define a name. The key value will be automatically generated.

In Strapi you can use the existing webhooks settings to configure the communication between.

Conclusion

Even if Strapi does not support to trigger GitHub Actions by default, it’s not a big deal to get it up and running. The Azure function is an easy way to get both systems connected. The only thing I struggled with was the missing user agent. If you keep that in mind, you should be ready in no time. If you want to learn more, check the source I’ve linked below.

Additional sources:

https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&pivots=programming-language-csharp#http-auth

https://docs.microsoft.com/en-us/azure/azure-functions/security-concepts?tabs=v4#function-access-keys

Laisser un commentaire