Jira Kanban Board with Typesafe GraphQL (Part 4)
This article is available as a screencast!
The first three parts of this series focused on building the back-end - now we move onto the frontend!
Note: if you are following along, I extended the ProjectResolver
a little bit since the previous article, so check out the GitHub repository to get the latest changes. You can find the source code here.
This article will focus on querying the API from the Vue app, building the select project dropdown, and a reactive store. As a reminder, the goal is a Kanban board like this:
Setting up Vite
I will use Vite, as opposed to the vue-cli, to develop the frontend. It’s much faster and has TypeScript support out of the box. Install it with yarn add vite --dev
. Since vite
is designed for loading ES modules, and for frontend development, some of the existing dependencies will cause problems. Move all the existing dependencies to devDependencies
. For more information on why, see the accompanying screencast.
I created a new file at the root of the project, index.html
with the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kanban</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts">
</script>
</body>
</html>
Note that Vite can load TypeScript out of the box. Great! In src/frontend/main.ts
, create a new Vue app:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
App.vue
is pretty simple, too:
<template>
<div>App</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
}
})
</script>
Loading Data
The next thing is to load all the projects
. I would normally use axios
for this, but axios
does not appear to have an ES build, so it won’t work with Vite without some extra work. Instead, I will use window.fetch
. The next question is how will we store the data? We could use the component local state, since this app is simple, but in my experience as apps grow, you need some kind of store. Let’s make a simple one using Vue’s new reactivity system.
A Simple Store
I will make a simple store. It will live in src/frontend/store.ts
. I have also defined SelectProject
interface, which will be used in the dropdown to select a project. src/frontend/types.ts
looks like this:
export interface SelectProject {
id: string
name: string
}
The store is like this:
import { reactive } from 'vue'
import { SelectProject } from './types'
interface State {
projects: SelectProject[]
}
function initialState(): State {
return {
projects: []
}
}
class Store {
protected state: State
constructor(init: State = initialState()) {
this.state = reactive(init)
}
getState(): State {
return this.state
}
async fetchProjects() {
// fetch posts...
}
}
export const store = new Store()
The store is powered by Vue’s new reactive
function, which makes an object reactive. We also define the initial state to have a projects
array, which will store the projects for the dropdown. How categories and tasks will be stored will be discussed later - for now we are just focusing on letting the user select a project.
Another improvement that will come in the future is to use provide
and inject
instead of exporting the store instance directly from the store. Stay tuned!
Adding CORS
During development, we will have two servers: the graphql server and the Vite dev server. To allow cross origin requests, we need to enable CORS. I did this in src/graphql/index.ts
using the cors
package:
// ...
import * as express from 'express'
import * as cors from 'cors'
(async() => {
// ...
const app = express()
app.use(cors())
// ...
app.listen(4000)
})()
Making a GraphQL Request with fetch
You can use a library like vue-apollo
to manage your GraphQL requests, but I’d like to keep things simple for this example. We will just use fetch
. Update the store’s fetchProjects
function
async fetchProjects() {
const response = await window.fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: `
{
projects {
id
name
}
}`
})
})
const result: { data: { projects: SelectProject[] } } = await response.json()
this.state.projects = result.data.projects
}
Unfortunately fetch
does not have the nice generic types axios
does, so we need to type the request manually. No big deal.
The Select Project Dropdown
Create a new component <select-project>
:
<template>
<select>
<option v-for="project in projects" :key="project.id">
{{ project.name }}
</option>
</select>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { SelectProject } from './types'
export default defineComponent({
props: {
projects: {
type: Array as () => SelectProject[]
}
}
})
</script>
And use it in App.vue
:
<template>
<select-project :projects="projects" />
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { store } from './store'
import SelectProject from './SelectProject.vue'
export default defineComponent({
components: {
SelectProject
},
setup() {
return {
projects: computed(() => store.getState().projects)
}
}
})
</script>
Importing the store
instance is not ideal - the next article will show how to use dependency injection with provide
and inject
.
Conclusion
Although we just rendered a dropdown, which might not seem like much, we have set ourselves up for success by creating a store which will let our app scale, and are fetching the projects using fetch
from our GraphQL API. The next step is allowing the user to select a project, which will load the categories and tasks, as well as making our store implementation more robust with provide
and inject
.
Absolutely no unsolicted spam. Unsubscribe anytime.