Boost your Worker development experience with Miniflare Part I

Boost your Worker development experience with Miniflare Part I

In this Article

Overview

You probably know Cloudflare as one of the biggest Content Delivery Network services available out there. You might also know about their serverless computing solutions like Cloudflare Workers. What I really like about Cloudflare solutions is how little effort is required to manage and deploy a serverless webapp enabling developers to focus on building their application while Cloudflare takes care of the rest. In these two articles I am going to explain how to leverage Miniflare to smoothen up your Cloudflare worker development experience. I am going to start a project from scratch for a Vue application that will be hosted on Cloudflare Pages. The application is going to allow a user to login with GitHub and list all the repositories for the authenticated user. The GitHub login is going to be powered by a Cloudflare worker that I will build and deploy to Cloudflare via GitHub workflows.

Create a Cloudflare account

Just like everything else on Cloudflare, creating an account is straightforward and easy, just navigate to the sign up page to get started and select a free account. Cloudflare free offering is pretty generous. That’s all you need to do for now. We will get back to the Cloudflare Dashboard later. Let’s get the fun part begin.

Setting up the project

Before I start here is the node version I am using:

Miniflare requires at least Node version 16.7.0.

With that out of the way, let’s get the basics going.

$ mkdir github-login-app
$ cd github-login-app && yarn init -y && git init
$ touch .gitignore

I will use a monorepo setup therefore I am changing my package.json to:

{
  "name": "github-login-app",
  "version": "1.0.0",
  "private": true,
  "author": "Coderpunktech",
  "license": "MIT",
  "workspaces": ["packages/*"]
}

I have added node_modules and dist to the .gitignore file and committed those changes. Next I will setup the Vue application using Buefy as my css framework.

Setting up the Frontend

I am going to use the vue-cli for that and manually bootstrap my application:

$ cd packages
$ vue create webapp

Following the prompts I am selecting the feature I want manually

I then select Typescript as my main language, Vuex to manage the internal state of the app and Unit Testing.

From here I have selected Jest as the testing framework, the 2.x version for Vue and skipped adding Babel to keep my boilerplate lean. I have got my webapp generated, great! Now I am updating the name in the webapp/package.json to monorepo standards:

{
  "name": "@github-login-app/webapp",
  ...
}

I need to add some dependencies to my project like Buefy for my css framework, Fontawesome for my icons library, axios to perform http calls and a library to manage cookies with vue.

# from the packages/webapp folder
$ yarn add vue-cookies axios buefy
# To get fontawesome
$ yarn add @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/vue-fontawesome

Now to wire those dependencies I am going to add the following to the webapp/src/main.ts file:

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import VueCookies from 'vue-cookies'

Vue.config.productionTip = false

// wire Buefy
Vue.use(Buefy)

// wire vue cookies with a 2 days expiration value
Vue.use(VueCookies, { expire: '2d' })

// add the github icon to the fontawesome icons library
library.add(faGithub)
// add the font awesome component to vue
Vue.component('font-awesome-icon', FontAwesomeIcon)

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

Frontend Implementation

I am going to start by creating the following files under the components folder:

  • components/auth
  • components/auth/GithubLogin.vue
  • components/auth/store
  • components/auth/store/state.ts
  • components/auth/store/mutations.ts
  • components/auth/store/index.ts

Here is what my state definition looks like:

// state.ts
const token: string | null = null

export default {
  token
}
// mutations.ts
export default {
  setToken: (state: any, token: string) => {
    state.token = token
  }
}
// index.ts
import state from './state'
import mutations from './mutations'

export default {
  namespaced: true,
  state,
  mutations
}

Then wiring the store management to the src/store/index.ts file

import Vue from 'vue'
import Vuex from 'vuex'
import TokenModule from '../components/auth/store'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    'auth': TokenModule
  }
})

My GithubLogin component is really simple. Just a button that links to the github oauth authorization api. I will leave the clientId empty for now.

<template>
<div class="card">
  <div class="card-content">
    <div class="content">
      <p>
        Authenticate with github to see your repositories
      </p>
      <b-button 
        size="is-large"
        tag="a"
        href="https://github.com/login/oauth/authorize?client_id= ">
        <font-awesome-icon icon="fa-brands fa-github" />
        Login with GitHub
      </b-button>
    </div>
  </div>
</div>
</template>
<script lang="ts">
export default {
    
}
</script>
<style>

</style>

I have removed the HelloWorld component from my App.vue and added the GithubLogin component:

<template>
  <div id="app">
    <github-login v-if="$store.state.auth.token === null"/>
    <div v-else>list the repos</div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import GithubLogin from './components/auth/GithubLogin.vue';

@Component({
  components: {
    GithubLogin
  },
})
export default class App extends Vue {}
</script>

<style>
...
</style>

When running my application this is what I see:

Now I am going to implement my Github Repos component. The component is going to load all the repos by calling the github api

<template>
  <div class="container hero">
    <div v-if="!loading && error === ''">
      <div class="card custom" v-for="(repo, $index) in repos" v-bind:key="$index" @click="open(repo.html_url)">
        <div class="card-content">
          <p class="title"></p>
          <div class="media">
            <div class="media-left">
              <figure class="image is-48x48">
                <img :src="repo.owner.avatar_url" :alt="repo.owner.login">
              </figure>
            </div>
            <div class="media-content">
              <p class="title is-4"></p>
              <p class="subtitle is-6"></p>
            </div>
          </div>

          <div class="content">
            <p v-if="repo.description !== null">
              
            </p>
            <p v-else>
              No description.
            </p>
          </div>
        </div>
      </div>
    </div>
    <div v-if="!loading && error !== ''">
      <b-message type="is-danger">
        
      </b-message>
    </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
import axios from 'axios'

export default Vue.extend({
  data() {
    return {
      repos: [],
      loading: true,
      error: ''
    }
  },
  methods: {
    open(url: string) {
      window.open(url)
    }
  },
  mounted() {
    const token = this.$store.state.auth.token
    this.error = ''
    axios({
      method: 'get',
      url: 'https://api.github.com/user/repos',
      headers: {
        accept: 'application/json',
        Authorization: `Bearer ${token}`
      }
    }).then((response) => {
      this.loading = false
      this.repos = response.data
    }).catch((err) => {
      this.loading = false
      this.repos = []
      this.error = 'Error fetching the user repos'
      console.error(err)
    })
  }
})
</script>
<style lang="css">
.custom {
  width: 300px;
  display: inline-table;
  margin: 5px;
  height: 350px;
}
</style>

All I have to do left now is to add the component to my App.vue. I am also going to add a mounted hook where I am going to use vue cookies to try and read the cookie that the worker will set once the user is authenticated. Here is what that looks like

<template>
  <div id="app">
    <github-login v-if="$store.state.auth.token === null"/>
    <github-repos v-else/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import GithubLogin from './components/auth/GithubLogin.vue';
import GithubRepos from './components/GithubRepos.vue'

@Component({
  components: {
    GithubLogin,
    GithubRepos
  },
  mounted() {
    const bearerToken = this.$cookies.get('bearer')
    this.$store.state.auth.token = bearerToken
  }
})
export default class App extends Vue {}
</script>
...

With the Frontend implementation complete I can now focus on implementing the worker. Follow Part II for the worker implementation and app deployment to Cloudflare.