Nick lazily jumps over the full stack

How to connect to Mailerlite API through Cloudflare Workers

For my subscription form, I use the Mailerlite service. When I had a mailing form, I used the Mailerlite service. To connect the form to the site, the service offers a Javascript-based widget. However, on my website built on Vue.js, the form was displayed only after the initial page load. And since I'm just learning Vue and want to create something cool instead of digging into incompatibilities, I decided to make my own form. After all, the API to the service is available.

The API uses a secret key that cannot be embedded into the static website so I needed a back-end. This is where Cloudflare Workers comes on the scene. Just imagine: you upload a piece of Javascript code that creates an API endpoint as if you configured your own server. But this code is deployed to the global Cloudflare's network, does not require any infrastructure on your side, and is free. This is too good to not use it!

The front-end

The subscription form is a Vue component that calls /api/subscribe URL to submit the email.

The back-end

This is the Javascript code that works in Cloudflare Workers. It monitors the POST requests to the /api/subscribe endpoint and forwards them to the Mailerlite API.

addEventListener("fetch", event => {
    event.respondWith(handleRequest(event.request))
})

const corsHeaders = {
    'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
    'Access-Control-Request-Method': 'POST, GET',
    'Access-Control-Request-Headers': '*',
}

async function handleRequest(req) {
    // OPTIONS request
    if (req.method === 'OPTIONS') {
        return new Response('OK', {headers: corsHeaders});
    }

    // POST /api/subscribe
    if (req.method === 'POST' && /api\/subscribe$/.test(req.url)) {
        // Parse request body as `FormData` instance
        let fdata = await req.json();
        const url = `https://api.mailerlite.com/api/v2/groups/${MAILERLITE_GROUP_ID}/subscribers`;

        const resp = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-MailerLite-ApiKey': MAILERLITE_API_KEY
            },
            // cache: 'no-store',
            body: JSON.stringify({
                email: fdata.email || ''
            })
        });

        const data = await resp.json();

        return new Response(JSON.stringify(data), {
            headers: {
                'Content-Type': 'application/json',
                ...corsHeaders
            }
        });
    }

    // default response
    return new Response(JSON.stringify('hi'), {
        headers: {
            "Content-Type": "application/json",
            ...corsHeaders
        },
    });
}

The Workers documentation is great and will explain how everything works. Also, it has a lot of examples. But there are some non-obvious points that are worth mentioning:

  • CORS headers must be added to every response from Workers. Otherwise, OPTIONS requests will be failing.
  • Besides the CORS headers, rate limiting is a good way to secure the back-end, at least in this specific case. Cloudflare provides free rate limit options.
  • The script uses several environment variables that should be defined in the Workers project:
    • ALLOWED_ORIGIN — the domain of the website with the subscription form.
    • MAILERLITE_GROUP_ID — the id of the group from the Mailerlite account.
    • MAILERLITE_API_KEY — the API key.

* * *

Now about the background picture and what I learned this time. To bring something personal, I used my ancient pen tablet and wrote 'Dear Subscriber'. However, at first the results were very ugly: the cursor was shaking, and the lines were not smooth. Not sure if that's my pen tablet, or Windows drivers, or something else. But I found something that helped me to write naturally. It is Lazy Nezumi: software with a completely wild number of parameters and features. Luckily, the default mode was the one I needed. It turned out really well, in my opinion.

See the picture: