Basic Concepts and Project Setup

At this point you already know how awesome GraphQL is, why you should use it and you’re dying to learn how to apply it in a project with your favorite frontend framework. If by chance that framework is Vue.js, you’re in the right place. We’re going to build a nice forum with it and learn some sweet stuff on the road. In case you haven’t used Vuejs yet, this is the perfect place to start.

An important note before continuing: this lesson doesn’t cover the creation of the server. If you want to learn how to build one before digging into the client, I recommend you to check the previous lessons. You’re going to enjoy them :)

Vue.js

Vue is a progressive framework for building user interfaces. The core library is focused on the view layer only, and it’s easy to pick up. Unlike other frameworks where official libraries or core dependencies are provided by the community, in Vue the principal libraries like Vuex, Vue-apollo or Vue-loader (to name a few) are maintained by the developers of the framework. This means all of the important libraries are guaranteed to be updated constantly, which is great. Vue is easy but also very powerful and the performance of the apps that you build with it is really quite good.

A great part of Vue’s growing popularity is due to their awesome documentation. It’s some of the best documentation around; everything is crystal clear and it address the great majority of use cases for every feature you may need. The great documentation extends to the official libraries as well. Having good documentation makes it easy to get started with Vue and progress smoothly while you play with the framework and see what it’s capable of. Also, last but not least, Vue is light - lighter than the majority of popular frameworks. Now that we have some context, Let’s get started!

Setup

The first thing we need to do is create our project. We’re going to use the vue cli which is the best way to start a project quickly. It allows you to set up important libraries easily and configure a starter project. To use it we need Vue.js and @vue/cli:

npm install -g vue @vue/cli

Now we can use vue create. This command will help us to create a nice starter project. We execute it and now we see two options, one to choose a default config, and another to manually select features:

Let’s talk about our choices.

Babel: Allow us to use ES6+ features Router: to have a basic routing configuration in place and added to our vue instance. CSS Pre-processor: I love pre-processors because they make styling a lot faster, easier and more readable. They allow us to use functions, declare variables, create mixins, extend classes, import files, and more, which helps us to avoid as much as possible the natural repetitiveness of css. Linter: Every project needs a linter in my opinion. Linters help you ensure that the code is correct and consistent. They analyze our code and tell us about errors or issues, according to the rules we set. Unit Testing: Testing is very important to ensure that our app, you know, works.

TypeScript

In addition to Vue, we’ll be using TypeScript on this project, which is a superset of JavaScript. This means that it contains all of the functionality of JavaScript and adds some new features. What TypeScript offers us is more control over our code via type annotations, interfaces, and classes. JavaScript is dynamically typed, which means that programs written in JavaScript do not know the data type of a variable until it is assigned a value at runtime and these variables can change type during runtime. This causes bugs that are easily overlooked, especially in large projects.

TypeScript allows us to define optional static type variables, specifying their types as String or Boolean. It will check types at compile time, and throw an error if the variable is given a value of a different type. It will not stop your code from compiling into plain JavaScript and running normally, it will just alert you when something looks off, similar to a linter. A static type variable looks like this in Vue.js: private msg!: string

We can also define interfaces, which allows us to tell the compiler “treat anything with a string name field and a number age field as a Person”, for example:

interface Person {
 name: string,
 age: number
}

const SomePerson: Person = {
 name: 'Bar',
 age: 17,
}

TypeScript also allows us to use classes and take advantage of inheritance, if that’s something you like:

export default class HelloWorld extends Vue {
  @Prop()
 public msg!: string
}

TypeScript helps your app scale more easily and promotes a consistent typed system which will help you catch bugs before they get to runtime. It also has features that help reduce code duplication, such as annotations.

Continuing Installation

Let’s get back to our installation process. After continuing, we have to select other features. We choose the following:

Class Style component syntax, to have decorators enabled by default. History mode in router to get rid of the /#/ from the url We choose Stylus as our css pre-processor We choose a basic linter and execute it on save We choose Jest as our test tool And we choose to place configs into their own files

We continue and the command will download all the files we need and setup the basic configuration. After a while our project is ready to start. If we inspect it, we notice that we have a clean directory structure:

|- root
  |- node_modules
  |- public
  |- src
     |- assets
     |- components
     |- views
  |- tests

Inside there’s a lot of stuff happening so let’s go file by file and see what they do:

tsconfig.js is our configuration file for TypeScript jest.config is our configuration for tests babel.config is our babel configuration tslint.json is our linter configuration

Now let’s inspect the directories. In our tests directory we have a test example. If we dig into src we can see our main.ts which is the entry file that renders the app. We can see a file called App.vue which is the primary layout, and checking this file we can see that it has a router-view component defined which renders a couple of views. The router is defined in a router.ts file.

We also have a couple of “shim” files that help us emulate Vue and JSX features in older browsers. We’re not going to use JSX, so let’s delete it. We have a components directory, and a public directory where our index.html file is located. Finally, we have a basic readme with instructions to run the app. If we use yarn serve, we will see our project running. Now let’s really get started.

Refactoring the Project

What we’re going to do now is change our directory structure to make it more robust and divide the code to make it cleaner. Then we will add the necessary configuration for some libraries that weren’t added when we created the project.

First, let’s rename the views directory to layouts. We create a directory called pages, which is going to contain children of the layouts. For example, we can have a Person layout, and inside pages have Persons Index and Person Show. Then we create a directory for the router.

We need to add some rules to our linter config. Open tslint.json and add the following:

  "rules": {
   ...
   "semicolon": [true, "never"],
   "space-before-function-paren": true,
   "space-within-parens": true,
   "no-console": false,
   "max-line-length": false
 }

“Semicolon”: to avoid using unnecessary semicolons “Space-before-function-paren” and “Space-within-parens” because I like the way that looks “No-console”: set it false to be able to use console.log "Max-line-length" to avoid issues with long lines of code.

This configuration is up to you of course - pick something that you like, and this will enforce your chosen style.

The configuration of vue-router is pretty straightforward. The routes are an array of objects, so we can separate them from the main class, and just import them. To achieve that we just need to create a route.ts file with our routes. Each route is an object, and we specify the path, the name, the component and children of the route. Then we can manage that using router-view. You can read more in the vue router guide.

export default [
 {
    path: '/',
    name: home,
    component: () => import('../layouts/Home.vue),
 },
 {
    path: '/about',
    name: about,
    component: () => import('../layouts/About.vue),
  },
]

Next, we’ll create an index.ts file and define our router there with our imported routes

import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'

Vue.use(Router)

export default new Router({
 mode: 'history',
 base: process.env.BASE_URL,
 routes,
})

Now that we have our routes correctly configured, let’s separate the style from the logic. We just need to create a styles directory containing a file named app.styl. Inside we add a basic style to see whether it works or not, and then in src/App.vue we import the file.

// src/assets/styles/app.styl
body
 background-color #f5f5f5
// src/App.vue
<script>
  import './assets/styles/app.styl'
</script>

If we save, TypeScript will throw an error: Module '"../src/App.vue"' has no default export.. This happens because we’re not exporting anything. To fix this, we just need to export our App class.

<script>
  import Vue from 'vue'
 import Component from 'vue-class-component'
 import './assets/styles/app.styl'

 @Component({
  name: 'App'
 })
 export default class App extends Vue {
 }
</script>

Now we need to install 'vue-class-component. I prefer the performance of this library over vue-property-decorator, but you can use either of these. Now that we have no errors, let’s add Pug to our project. Pug is a high-performance template engine with a clean, whitespace-sensitive syntax for writing HTML. To see it in action, we just need to add lang=”pug” to the template tag. Now look at the difference between our current template:

<div id="app">
 <div id="nav">
   <router-link to="/">Home</router-link> |
   <router-link to="/about">About</router-link>
 </div>
 <router-view/>
</div>

And our new template

<template lang="pug">
 div#app
   div#nav
     router-link(to="/") Home |
     router-link(to="/") About
   router-view
</template>

It’s clear that the syntax is a lot more terse when using pug, but if we save this, we’re going to get an error because we haven’t installed it yet. Let’s install Pug. Luckily for us, vue-cli has a command that will add and configure Pug into our project:

vue add pug

This will work out of the box. If we save the file, our hello page is shown again. But I want to do something more. I’m a big fan of having everything separated, so let’s separate our templates from our logic as well. First, we create a templates directory. Inside I will create an App.pug file and inside I will paste the template from App.vue. Let’s also delete the script tags and rename App.vue to App.ts

// src/templates/App.pug
div#app
 div#nav
   router-link(to="/") Home |
   router-link(to="/") about
 router-view
// src/App.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import './assets/styles/app.styl'

@Component({
 name: 'App'
})
export default class App extends Vue {
}

We save, and we have an error:

You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

This is because we’re trying to render a template directly, i.e. passing a string to the template option in this case. Ultimately, we should probably pre-compile these templates into render functions, but for now we’ll install the full build of Vue.js which includes the compiler. The easiest way to fix this issue is by creating an alias inside a vue.config.js file.

module.exports = {
 configureWebpack: {
   resolve: {
     alias: {
       'vue$': 'vue/dist/vue.js'
     }
   }
 }
}

The project we generated uses webpack under the hood, and we can use the configureWebpack option to extend the default configuration with our own modules and rules. Here we defined an alias, which allows us to use the full build of vue to compile our templates. If we save now, we can see our template rendered again. Nice! We have all the basics in place, it’s time to start creating our forum.

Apollo

We need some dependencies to be able to use GraphQL in our Vue client, the principal one being Apollo. Apollo is a production-ready set of tools and libraries, built upon the GraphQL specification, which implement features, techniques and feedback from the developers and their partners and the community. It’s a fully-featured caching GraphQL client with integrations for React, Angular, Vue and more. It allows you to easily build UI components that fetch data via GraphQL.

You have two options to add Apollo to your project: using the vue command vue add apollo or doing it manually. While the command is great and it gives you some great features out of the box, I’m a lot more comfortable adding it manually and having complete control over the plugins and configuration I’m using. First, let’s add our dependencies:

"dependencies": {
    "apollo-cache-inmemory": "1.3.8",
    "apollo-client": "2.4.5",
    "apollo-link": "^1.2.2",
    "apollo-link-context": "^1.0.8",
    "apollo-link-http": "^1.5.4",
    "apollo-utilities": "1.0.25",
    "graphql": "^14.0.2",
    "graphql-tag": "^2.10.0",
    "vue": "^2.5.17",
    "vue-apollo": "^3.0.0-beta.25"
  }

That’s quite a few dependencies, let’s see what they do:

vue-apollo: An Apollo/GraphQL integration for VueJS. We install the latest version of the plugin which allows us to use all the great features that come with Apollo client 2.0. graphql: A reference implementation of GraphQL for JavaScript. graphql-tag: A JavaScript template literal tag that parses GraphQL queries. apollo-client: A fully-featured, production ready caching GraphQL client for every server or UI framework. apollo-link: A standard interface for modifying control flow of GraphQL requests and fetching GraphQL results. apollo-link-context: Used to easily set a context on your operation, which is used by other links further down the chain. apollo-link-http: Used to get GraphQL results over a network using HTTP fetch. apollo-cache-inmemory: Cache implementation for Apollo Client 2.0.

Once we have our dependencies installed, we create a directory called graphql and inside, we’re going to create a client.ts file and add the following code:

import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
import { setContext } from 'apollo-link-context'

Vue.use(VueApollo)

const httpLink = new HttpLink({
 uri: 'http://localhost:4000',
})

const authLink = setContext((_, { headers }) => {
 const token = localStorage.getItem('token')
 return {
   headers: {
     ...headers,
     authorization: `Bearer ${token}`,
   },
 }
})

const apolloClient = new ApolloClient({
 link: authLink.concat(httpLink),
 cache: new InMemoryCache(),
})

const apolloProvider = new VueApollo({
 defaultClient: apolloClient,
})

export default apolloProvider

We import all the libraries we’re going to use, and then we tell Vue to use VueApollo and create the link we’re going to use to communicate to our server.

const authLink = setContext((_, { headers }) => {
 const token = localStorage.getItem('token')
 return {
   headers: {
     ...headers,
     authorization: `Bearer ${token}`,
   },
 }
})

Here we set a context, this will check if a user is logged in, and add an authorization header if they are.

const apolloClient = new ApolloClient({
 link: authLink.concat(httpLink),
 cache: new InMemoryCache(),
})

const apolloProvider = new VueApollo({
 defaultClient: apolloClient,
})

export default apolloProvider

Finally, we initiate a client with a link that combines our authLink and httpLink.. This will take care of the user’s token in each request. Finally we define a new apollo provider which is an instance of VueApollo that uses that client by default, and export it. Now that we have our client ready, we import the provider and add it to our main.ts

// src/main.ts
import Vue from 'vue'
import App from './App'
import router from './router'
import apolloProvider from './graphql/client'

Vue.config.productionTip = false

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

Note: Vue.config.productionTip = false just hides a warning that Vue shows in the terminal when we are in development mode.

Sometimes TypeScript can complain with errors about not recognizing graphql types, this can be fixed by creating a new module for TypeScript manually or installing @types/graphql.

Building the app

Signup

Now that we have our configuration in place, let’s test it with a couple of components. We’re building a forum where users can create and see categories, threads and posts. Without users, there’s no data, so the first thing we need to do is build the signup process.

I will use the server we built previously. I’ll start the server by running yarn dev. Now let’s go to our graphql directory. Inside we will create files to store our mutations and queries in: Queries.ts and Mutations.ts.

Inside these files we’re going to define our graphql requests. First we’re going to work with the mutations. We’ll import graphql-tag to allow us to use the gql string literal when writing our requests.

import gql from 'graphql-tag'

export default class Mutations {
 // Users
 public authenticate () {
   return gql `
     mutation authenticate($email: String!, $password: String!) {
       authenticate(email: $email, password: $password)
     }`
 }

 public createUser () {
   return gql `
     mutation createUser($name: String!, $password: String!, $email: String!,        $username: String!) {
       createUser(
         name: $name,
         username: $username,
         email: $email,
         password: $password
       ) {
         id
       }
     }
   `
 }
}

We added two mutations. authenticate requires email and password fields of type string. createUser takes four params: name, email, password and username, all of type string.

We have our mutations, now let’s create our layout. In the layouts directory we’re going to modify one of the components that were created with the project. I’ll rename it to Signup, and then we’ll change the name of the route and change the link in App.pug to match this new route.

// src/router/routes.ts
{
   path: '/signup',
   name: 'signup',
   component: () => import('../layouts/Signup'),
},
// src/templates/App.pug
div#app
 div#nav
   router-link(to="/") Home |
   router-link(to="/") about
 router-view

After that we create the corresponding template, and declare a simple class.

// src/templates/Signup.pug
div.about
// src/layouts/Signup.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
 name: 'Signup',
 template: require('./templates/Signup.pug'),
})
export default class Signup extends Vue {
}

Now we can visit our signup page. Let’s add a couple of libraries from a cdn: we’ll use Pure CSS to quickly build our forms, Fontawesome for icons, and Roboto as the main font. I’ll add them to index.html, and I’ll delete all the unnecessary stuff from App.pug.

// public/index.html
<head>
    ...
    <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
   <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
   <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
   <title>vue-graphql</title>
  </head>
// src/templates/App,pug
div#app
 router-view

Let’s move on to the important stuff. There are various ways to use GraphQL in a vue project:

For Queries

  • Using apollo object in a component decorator or export default object.
  • Using this.$apollo.query
  • Using Apollo Components

For Mutations

  • Using this.$apollo.mutate
  • Using Apollo Components

Each way has its own advantages and use cases. The good news is that we’re going to see how all of these work. I recommend understanding the pros and cons of each of them and using them according to your needs. Now, let’s see how we can use this.$apollo to create our signup process. First, we' create a signup template and inside we add a form::

// src/templates/Signup.pug
main.section
 h1.top-title Signup
 form(
   @submit.prevent="signup"
   class="pure-form pure-form-stacked"
   method="POST"
 )
   fieldset.pure-1
     input.pure-u-1(
       id="name"
       placeholder="Name"
       type="text"
       v-model="name"
     )
   fieldset.pure-1
     input.pure-u-1(
       id="email"
       placeholder="Email"
       type="email"
       v-model="email"
     )
   fieldset.pure-1
     input.pure-u-1(
       id="password"
       placeholder="Password"
       type="password"
       v-model="password"
     )
   fieldset.pure-1
     input.pure-u-1(
       id="username"
       placeholder="Username"
       type="text"
       v-model="username"
     )
   fieldset.pure-1
     button.pure-button.pure-input-1(
       type="submit"
     ) Sign up

It’s a simple signup form with an input for every field we need. Every input has a v-model, which is a Vue directive that allow us to set a reactive variable that we’re going to use to set and get the values. Then we use @submit.prevent to prevent the default behavior of the signup and execute the method we define instead.

If we save this, we’re going to see a bunch of errors because we’re using variables we haven’t defined, so let's define them:

// src/layouts/Signup.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import Mutations from '../graphql/mutations'

const Mutation = new Mutations()

@Component({
 name: 'Signup',
 template: require('../templates/layouts/Signup.pug'),
})
export default class Signup extends Vue {
 public email: string = ''
 public password: string = ''
 public name: string = ''
 public username: string = ''
}

Just like before, we create a class for the component. Inside of it we define our typed variables and also we create an instance of our Mutations class.

The form doesn’t look good, we need to add some styles to it. I’m using Stylus, which is a CSS preprocessor. It allow us to use great features that aren’t available (yet) in CSS or don’t have full compatibility, like variables, functions, extend classes, loops, conditionals, etc. The good news is that it’s already included in our project thanks to vue-cli.

In our assets directory we’ll create 3 files: _settings.styl, _tools.styl and _elements.styl. We’ll also create a directory called components, and we define some styles and import them inside app.styl. I base this configuration on a methodology called ITCSS, which is great and which I currently prefer.

// src/assets/_settings.styl
bg = #f5f5f5
primary = #DB3440
action = #FAAF3F
border_bottom = .075rem solid rgba(black, .15)
titleColor = rgba(black, .75)
subtitleColor = rgba(black, .55)

mobile = 35.5rem
desktop = 64rem

Inside Tools we’re going to define a couple of mixins: one to define media queries easily, and another to assign flexbox to the elements.

// src/assets/_tools
media(size)
 @media (min-width: size)
   {block}

flex(align, justify)
 align-items align
 display flex
 flex-wrap wrap
 justify-content justify

In _elements we define our common styles, like the ones for the body. Finally we have our components directory, where we define reusable classes. I’m going to add a.top-title component.

// src/assets/components/_top-title.styl
.top-title
 font-size 2.5rem
 font-weight normal
 margin 2rem 0 4rem
 text-align center
 width 100%

// src/assets/app.styl
@import '_settings'
@import '_tools'
@import '_elements'
@import 'components/*'

I’m not going to dig too much into stylus or styles in general, because it’s not our focus, but it’s good to know how to organize our styles in a vue project. All the components and layouts have styles I created ahead of time, and you can check them out in the public repository.

OK, let’s continue. We’ve added styles and mutations. We want our users to be logged in after signup, so we’re going to define two methods: one for signup and another for login if signup is successful:

  public signup (): void {
   this.$apollo
     .mutate({
       mutation: Mutation.createUser(),
       variables: {
         email: this.email,
         name: this.name,
         password: this.password,
         username: this.username,
       },
     })
     .then(async (response: any) => {
       console.log(response)
       alert(`${this.name} was registered successfully`)
       await this.signin()
     })
     .catch((error: any) => {
       console.log(error)
     })
  }

We define a method with type void because we aren’t returning anything. We use this.$apollo.mutate(), then we define our mutation selecting the respective computed property defined in our Mutations class. After that we pass our models as variables. Then we define a method for successful signup, which basically signs in the user. We also define another method to catch any errors. Let’s add a signin method as well:

  public signin () {
   this.$apollo
     .mutate({
       mutation: Mutation.authenticate(),
       variables: {
         email: this.email,
         password: this.password,
       },
     })
     .then((response: any) => {
       localStorage.setItem('token', response.data.authenticate)
       this.$router.replace('/')
     })
     .catch((error: any) => {
       console.log(error)
     })
  }

As you can see this is basically the same, but we’re redirecting the user in the successful callback using vue-router. Now let’s test our signup:

And it works! Here’s what happens under the hood.

Our models take their respective values from the inputs when we click the signup button, those values are sent to our signup method in the signup method, we assign our model values to the variables we need in the mutation and execute it. Once the user has signed up, we execute the signin function. This uses the email and password the user just filled in, to call the authenticate mutation. Finally, the user is redirected to our homepage, which is categories.

Login

Great! Now we can go through the same process with the login page. Let’s copy our signup template, remove the inputs for username and name and add a link to signup in case the user is not registered. Instead of signup, we’ll call the signin method in @submit.prevent

// src/templates/signin.pug
main.section
 h1.top-title Forum with Vue, TypeScript and GraphQL
 form(
   @submit.prevent="signin"
   class="pure-form pure-form-stacked"
   method="POST"
 )
   fieldset.pure-1
     input.pure-u-1(
       id="email"
       placeholder="Email"
       type="email"
       v-model="email"
     )
   fieldset.pure-1
     input.pure-u-1(
       id="password"
       placeholder="Password"
       type="password"
       v-model="password"
     )
   fieldset.pure-1
     .pure-form-message
       router-link.link(to="/signup") Don't have an account?
     button.pure-button.pure-input-1(
       type="submit"
     ) Sign in

And in our Signin.ts file, we get rid of the signup mutation and the extra models we don’t need, and also refactor a little:

// src/layouts/Signin.ts
@Component({
 name: 'Signin',
 template: require('../templates/layouts/Signin.pug'),
})
export default class Signin extends Vue {
 public email: string = ''
 public password: string = ''

 public signin () {
   this.$apollo
     .mutate({
       mutation: Mutation.authenticate(),
       variables: {
         email: this.email,
         password: this.password,
       },
     })
     .then((response: any) => {
       localStorage.setItem('token', response.data.authenticate)
       alert('Welcome!')
       this.$router.push('/')
     })
     .catch((error: any) => {
       console.log(error)
     })
 }
}

The only difference is that we show a different alert in our success callback and we use a different name, class name and template. If we try to login now:

Everything works fine, great!

Vuex

OK, we are already checking if the user is logged in to perform server requests, but we also need to add a configuration to manage authorization for the routes. An easy way to do it, in case we don’t have a lot of views, is to use a conditional that verifies if the token exists in the localStorage every time the user changes routes. If there’s a token, we can set a global variable that we can use in any part of app. The typical way to do this in Vue is by using Vuex.

Vuex is a state management pattern library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

What’s a state management pattern?

First we need to understand what state is. When building applications there are lot of cases when you’d need or want to have temporary data, accessible throughout all your app. A common example would be a temporary login state, which we can manage by having a variable that stores an authentication token. Once the user closes the app or logs out, that variable lose its value. As the application grows larger, managing these state variables can become difficult. That’s when we need a State Management Pattern.

The Vuex pattern has the following parts:

A state, which is basically a variable we can update when needed. A method to get the state, called getter A method to update or change the state, called mutation And a way to call those methods, called actions.

You can read more about this topic in their official documentation . Now let’s add Vuex to our app. To do so we just need to run vue add vuex. This command will generate a basic store for us, and add it to our main.ts file. If we check the new file, we notice it has an object with the getters, actions and mutations defined directly inside of it.

This is nice for small applications where simplicity is better, but for large apps it can be a pain to scale. Let’s divide this config and use a modular approach. What we’re going to do is create a directory called store, inside we’re going to create an index.ts file which is going to contain our config. We also create a file for the types, state, getters and mutations. Lastly we create a module.


// src/store/index.ts
import Vue from 'vue'
import Vuex, { StoreOptions } from 'vuex'
import { RootState } from './types'
import { AppModule } from './appModule'

Vue.use(Vuex)

const store: StoreOptions<RootState> = {
 state: {
   version: '0.0.1',
 },
 modules: {
   AppModule,
 },
}

export default new Vuex.Store<RootState>(store)

In our index.ts file we define and export our store. Notice we’re defining a module in the store object. A module in this context represents a specific configuration that will be used in a specific context, ie: we could define a module Users to manage state variables like the user’s full name. In our case, the module is a simple general one, because we’re just managing the login state.

Another important thing is the way we’re creating the store. We use StoreOptions to explicitly define the type of the Vuex root instance, which is RootState in this case, which is an interface we’re going to define in types.ts

// src/store/types.ts
export interface RootState {
 version: string
}

export interface AppState {
 loggedIn: boolean
}

We create two interfaces: one for the root vuex instance with a simple variable version of type string, and another one which represents the state model of our App. Let’s define a default initialization state:

import { AppState } from './types'

const state: AppState = {
 loggedIn: false,
}

export default state

We create and export a constant state of type AppState and assign a default value to our login state.

// src/store/mutations.ts
import { MutationTree } from 'vuex'
import { AppState } from './types'

const mutations: MutationTree<AppState> = {
 setLogin (state, value: boolean) {
   state.loggedIn = value
 },
}

export default mutations

In the mutations, the differences are bigger. In a standard configuration you will see something like: export default mutations = { … }, but we’re using typescript so we need a little more code. First, we import MutationTree which we’ll parameterize with the AppState interface we defined before. This will set the state type that our mutations will use. Then we create a constant named mutations that uses our interface. Inside we define a setLogin method that updates the login state with a new boolean value.

// src/store/getters.ts
import { GetterTree } from 'vuex'
import { AppState, RootState } from './types'

const getters: GetterTree<AppState, RootState> = {
 getLogin (state): boolean {
   const { loggedIn } = state
   return loggedIn
 },
}

export default getters

The getters are similar to the mutations, but we use GetterTree in the constant instead of MutationTree. It receives two params: the state of our module, and the root state of the vuex instance. We also add a getter that returns the current value of our login state, notice we’re specifying the type of the data we’re going to return.

// src/store/AppModule.ts
import { Module } from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import { AppState, RootState } from './types'

export const AppModule: Module<AppState, RootState> = {
 state,
 getters,
 mutations,
}

Finally we create our module. We import the mutations, the getters, the types and the state. We create a constant called AppModule which receives the state of the module and the root state of the vuex instance. Inside we pass the state, getters and mutations.

Our configuration is done, but the store is not ready to use yet...at least not in the way we want. We would like to use decorators, and there are various ways to do that. The easiest way is to install vuex-class which is a binding helper for Vuex and vue-class-component. To install it just run: yarn add vuex-class. Now we’re ready to use our store.

To manage the login state, we’re going to add some code to our App.ts file.

// src/App.ts
...
import { Watch } from 'vue-property-decorator'
import { Mutation } from 'vuex-class'

@Component({
 name: 'App',
 template: require('./templates/App.pug'),
})
export default class App extends Vue {
 @Mutation('setLogin') public setLogin
}

First we import Watch, which is a decorator that allow us to dynamically check changes in variables. Then we get our mutation method using the decorator from vuex-class. Notice that we’re getting an error saying that we need to assign a type any to our function. We’ll just disable this warning for now in tsconfig.json,

“compilerOptions”: {
  …
  “noImplicitAny”: false,
  ...
}

Now we can use the mutation inside our App class:

export default class App extends Vue {
 @Mutation('setLogin') public setLogin

 public checkLogin (): void {
   if (localStorage.getItem('token')) {
     this.setLogin(true)
     if (this.$router.currentRoute.name === 'signin' || this.$router.currentRoute.name === 'signup') {
       alert('You already have a session.')
       this.$router.push('/')
     }
   } else if (this.$router.currentRoute.name && this.$router.currentRoute.name === 'newThread' || this.$router.currentRoute.name === 'newPost') {
     alert('You need to be logged for this action')
     this.setLogin(false)
     this.$router.push('/')
   } else {
     this.setLogin(false)
   }
 }

 @Watch('$route', {immediate: true, deep: true})
   public onUrlChange (newVal: any) {
     this.checkLogin()
   }
}

Then we define two methods.

The first one checks if a token exists in the localStorage. If it exists, we set our login state to true, otherwise we set it to false. It also checks our current route name, which we assigned in our router, to ensure that a logged in user cannot signup or login again. We also make sure that a user cannot visit any page that requires the user to be logged in. Here we use newThread and newPost, which are routes we will add later. The second method is a callback that will execute our checkLogin method everytime the route changes.

Now if we try to go to signup or login, we cannot, because it redirect us to categories. Nice!

Now that we’ve taken care of that, it’s time to continue with the other layouts.

Categories Layout

Our homepage will show all the categories. A user can create and see threads from a given category, and also create and see posts from a given thread. This means all the routes we need, like Thread Index, New Post, etc, are children of Categories. Before digging into graphql further, let’s add all the routes we need:

export default [
 {
   path: '',
   component: () => import('../layouts/Categories'),
   children: [
     {
       path: '',
       name: 'Categories',
       component: () => import('../pages/Categories'),
     },
     {
       path: '/categories/:id',
       props: true
       children: [
         {
           path: '',
           name: 'Threads',
           props: true
         },
         {
           path: '/categories/:id/threads/:threadId',
           name: 'Thread',
           props: true
         },
         {
           path: '/categories/:id/new-thread',
           name: 'newThread',
           props: true
         },
         {
           path: '/categories/:id/threads/:threadId/new-post',
           name: 'newPost',
           props: true
         },
       ],
     },
   ],
 },
 {
   path: '/signin',
   name: 'signin',
   component: () => import('../layouts/Signin'),
 },
 {
   path: '/signup',
   name: 'signup',
   component: () => import('../layouts/Signup'),
 },
]

We added all the routes, and we made threads children of categories. Notice that we added newPost and newThread, which are the routes we defined when checking the login state. Also notice that, except for Categories, we’re not assigning any component because we haven’t created them yet. From now on, whenever I say something like let’s add the route, I will be referring to adding the component to the route.

OK, now let’s create our home layout. As usual, we define a class and require the template:

// src/layouts/Categories.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
 name: 'Categories',
 template: require('../templates/layouts/Categories.pug'),
})
export default class Categories extends Vue {
}

We’ll create a template inside src/templates/layouts called categories.pug, and inside we’re just going to add a router-view.

// src/templates/layouts/categories.pug
router-view

We do this because this layout is going to be just a container. The actual views are going to be the children, and we need to create one to see all the categories. What we’re going to do now is go to our templates directory, and create a pages directory where our children views will be located, and there we create a categories.pug file.

To see categories, first we need to create them. This time we’re going to use a different approach than before. Instead of using this.$apollo, we’re going to use Apollo Components.

Apollo Components

Apollo components are components from the apollo folks that we can use to manage our data. They’re awesome, but you shouldn't abuse them. They have their use cases, but there are some moments when they’re not so great. We’ll see what I mean while building this component.

Let’s add our mutations first:

  // Categories
 public createCategory () {
   return gql `
     mutation createCategory($title: String!) {
       createCategory(title: $title) {
         id
         title
       }
     }
   `
 }

Now we edit our template to add the creation form:

// src/templates/pages/Categories.pug
main.section
 Toolbar(
   title="Categories"
 )
 apollo-mutation(
   :mutation="newCategoryMutation"
   :variables="{ title }"
   @done="categorySuccess"
   @error="categoryError"
   v-if="getLogin"
 )
   section.pure-form.-categories(slot-scope="{ mutate }")
     input.input.pure-input-1(
       id="title"
       placeholder="Title"
       type="text"
       v-model="title"
     )
     button.pure-button(
       @click="createCategory(mutate)"
     ) Create Category

There’s a lot of stuff going on there, let’s go part by part. apollo-mutation is the component for mutations that apollo providse us. Its API has all the options we need to work perfectly. Let’s see which ones we used:

:mutation specifies the mutation, this should be in gql format :variables specifies the variables we need in a given mutation, it can be a single variable or an object. @done specifies a callback that is triggered after successful request. @error specifies a callback that is triggered if there’s an error.

Apollo uses Scoped-Slots, Scoped-slots are a vue feature that allows developers to provide a component with a reusable slot that can access data from the child component, which mean we can define custom templates for certain props. In the case of apollo-mutation the props we can access are:

mutate: Function to call the mutation. You can override the mutation options (for example: mutate({ variables: { foo: 'bar } })) loading: Boolean indicating that the request is in flight error: Eventual error for the last mutation call gqlError: first GraphQL error if any

Now if we check the mutation component: :

 apollo-mutation(
   :mutation="newCategoryMutation"
   :variables="{ title }"
   @done="categorySuccess"
   @error="categoryError"
   v-if="getLogin"
 )
   section.pure-form.-categories(slot-scope="{ mutate }")

We can notice the values we assigned, newCategoryMutation is the mutation gql; categorySuccess and categoryError are our callback functions. For the variable we just pass a model variable. Below we can see our scoped div which tells Vue we’re using a custom template to use mutate.

     section.pure-form.-categories(slot-scope="{ mutate }")
     input.input.pure-input-1(
       id="title"
       placeholder="Title"
       type="text"
       v-model="title"
     )
     button.pure-button(
       @click="createCategory(mutate)"
     ) Create Category

After that we define an input which of course has a v-model defined with the required variable. Then we add a button that triggers a function on click, which receives mutate as an argument. Now we will create a Categories.ts file inside src/pages:

//src/pages/Categories.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import Mutations from '../graphql/mutations'

const Mutation = new Mutations()

@Component({
 name: 'Categories',
 template: require('../templates/pages/Categories.pug')
})
export default class Categories extends Vue {
  public title: string = ''
  public newCategoryMutation = Mutation.createCategory()

  public createCategory (mutate) {
   if (this.title !== '') {
     mutate()
   } else {
     alert('Name is required')
   }
 }

 public categorySuccess (result)  {
   alert(`${result.data.createCategory.title} was created successfully!`)
 }

 public categoryError (error) {
   console.log(error)
 }
}

Just like before, we create a class for our component. Inside we define a variable for the title of the category and another variable newCategoryMutation for our mutation. Then we define three methods, the first one receives the mutation method we sent from categories.pug and executes the mutation if the title isn’t empty. Our success callback just shows an alert. Then we log any errors in the error callback. Now if we try to create a category, it should work:

Nice! The good thing about apollo-components is that it makes everything direct and the code is clean. The downside is that the queries we use in those components aren’t added to the vue instance, which mean we cannot use them directly via this.$apollo. That allows us to pass queries as props to other components, and execute methods directly.

Showing Categories

So far we’ve only used mutations, so we haven’t created a Queries class. Let’s do that now and add the query to get the categories:

import gql from 'graphql-tag'

export default class Queries {
 // Categories
 public categories () {
   return gql `
     query categories($page: Int!, $perPage: Int!) {
       categories(pagination: {page: $page, perPage: $perPage}) {
         totalPages
         totalEntries
         page
         perPage
         entries {
           id
           title
           slug
         }
       }
     }`
  }
}

It’s the same logic we used with the mutations and, if we check the query, we notice that it receives two pagination params: a page number, and the quantity of items per page. Of course we’re going to use those variables to do server side pagination.

Now let’s go back to our Categories component and add the code we need. First we’ll add our new Queries class and assign an instance of it to a variable.

import Queries from '../graphql/queries'
const Query = new Queries()

Now let’s add apollo to our component decorator:

@Component({
 apollo: {
   categories: {
     query: Query.categories,
     variables () {
       return {
         page: this.page,
         perPage: this.perPage,
       }
     },
     error (error) {
       console.log(error)
     },
   },
 },
  ...
})

The syntax makes it easy to understand what’s going on. We define an object named apollo, inside of which we have another object categories. This is the variable that will get the data from the query, and the one we will use in our template. It receives several options, which are specified in [the vue-apollo docs] (https://akryum.github.io/vue-apollo/api/smart-query.html#options). We use query to define the query to get the categories. We also need some variables, and these variables need to be reactive because the user will change the page and load content dynamically.

To be able to use reactive variables (the ones defined in our class or in data ()) we have to use the function syntax of the variables option variables() {} and we return an object with variables for the page number and the number of categories per page.

return {
  page: this.page,
  perPage: this.perPage,
}

Variable Definition and Pagination Methods.

Now, inside our class we add the variables we just defined:

export default class Categories extends Vue {
  ...
  public active: number = 1
 public page: number = 0
 public perPage: number = 10
 public categories = ''
  …
}

We define a variable active to check what the current page is, another variable page to get the actual page, another variable perPage to define the number of categories per page. Then we define a variable categories of type any which is linked to the variable of the same name we just used in the apollo object and exists just to initialize it with an empty value instead of undefined. This should really just be an empty list, and it shouldn’t use type any - sorry about that.

The reason to have an active variable which seems the same as page is because when the query is successful we’ll get an array of categories that starts from index 0 from the server and the pages starts from 1, which means all our pages should be actually one number less, we need a variable to save the value of that difference to highlight the current page. We really should just store this once and compute the other value. Feel free to make that change if you’d like :)

We still need to add various things, like the methods to go to the next and previous page, the method to go to category view and to change the page directly. Let’s add all of them:

export default class Categories extends Vue {
  ...
  public goToCategory (categoryId: string) {
   this.$router.push(`/categories/${categoryId}`)
 }

 public prevPage (): void {
   this.page = this.page - 1
   this.active = this.active - 1
 }

 public nextPage (): void {
   this.page = this.page + 1
   this.active = this.active + 1
 }

 public goToPage (pageNumber: number) {
   this.page = pageNumber - 1
   this.active = pageNumber
 }
}

OK, we just added four methods, let’s see what they do.

goToCategory: Receives an id of type string, and goes to the category linked to that id prevPage: Decrement by one our current and active page. nextPage: Increment by one our current and active page. goToPage: Receives a number, and evaluates it, if it’s different from our active page, we go to the specified number minus 1.

Now that we have everything ready in our logic, let's complete our template:

main.section
 apollo-mutation(
   :mutation="newCategoryMutation"
   :variables="{ title }"
   @done="categorySuccess"
   @error="categoryError"
   v-if="getLogin"
 )
   section.pure-form.-categories(slot-scope="{ mutate }")
     input.input.pure-input-1(
       id="title"
       placeholder="Title"
       type="text"
       v-model="title"
     )
     button.pure-button(
       @click="createCategory(mutate)"
     ) Create Category
 ul.thread-list(
   v-if="categories && categories.entries.length > 0"
 )
   li.thread(
     @click="goToCategory(category.id)"
     :key="category.id"
     v-for="category of categories.entries"
   )
     section.thread-side
       i.icon.fas.fa-tags
     section.thread-main
       header.thread-header
         span {{ category.title }}
   ul.st-pagination(
     v-if="categories.totalPages > 1"
   )
     li(v-if="page === 0")
       i.page.fas.fa-angle-left
     li(v-else)
       a.link.page(@click="prevPage")
         i.fas.fa-angle-left
     li(
       v-for="pageNumber in categories.totalPages"
       :key="pageNumber"
     )
       a.link.page(
         :class="pageNumber === active && 'active'"
         @click="goToPage(pageNumber)"
       ) {{ pageNumber }}
     li(v-if="page === categories.totalPages - 1")
       i.page.fas.fa-angle-right
     li(v-else)
       a.link.page(@click="nextPage")
         i.fas.fa-angle-right
 ul.thread-list(v-else)
   li.thread
     section.thread-main
       header.thread-header There aren't any records to display.

That’s a lot of code, let’s see what’s happening here:

  ul.thread-list(
   v-if="categories && categories.entries.length > 0"
 )
   ...
   ul.thread-list(v-else)
   li.thread
     section.thread-main
       header.thread-header There aren't any records to display.

We check if categories have results. If it does, we show the categories. If not we show a message saying that there are no records.

     li.thread(
     @click="goToCategory(category.id)"
     :key="category.id"
     v-for="category of categories.entries"
   )
     section.thread-side
       i.icon.fas.fa-tags
     section.thread-main
       header.thread-header
         span {{ category.title }}

In case we have data, we map through it using the directive v-for rendering a list item for each result in the array, which just shows the category title. Each item contains a key which is a required prop we have to use when rendering inside a loop. We also provide a click method which will navigate to the view of each category.

     ul.st-pagination(
     v-if="categories.totalPages > 1"
   )
     li(v-if="page === 0")
       i.page.fas.fa-angle-left
     li(v-else)
       a.link.page(@click="prevPage")
         i.fas.fa-angle-left
     li(
       v-for="pageNumber in categories.totalPages"
       :key="pageNumber"
     )
       a.link.page(
         :class="pageNumber === active && 'active'"
         @click="goToPage(pageNumber)"
       ) {{ pageNumber }}
     li(v-if="page === categories.totalPages - 1")
       i.page.fas.fa-angle-right
     li(v-else)
       a.link.page(@click="nextPage")
         i.fas.fa-angle-right

We receive a field called totalPages from our query, and the first thing we do is check it to see if it is > 1. If so, the pagination component will be rendered. There’s not much sense in rendering a pagination component if there are no pages to visit. Then, we check if we’re on the first or last page, to determine whether we should activate the previous or next page links.

Finally, we check the page number to assign an active class and highlight the current page.

If we save this we’re going to see a nice category list:

Real Time Update with Apollo

Let’s create lot of random categories to see our pagination in action. We just type a title, create a category and wait...the view isn’t updating.

The good news is that we used apollo in our component decorator to call our query, which makes the fix super easy. You’re probably thinking “do what now?”

Well, remember earlier I said that there are different ways to use apollo and each has pros and cons? Well, one of the principal advantages of using apollo in the export object or the component decorator is that it’s initialized like a property of our component which means it’s added to this.$apollo.queries. This doesn’t happen when using apollo components, which makes it a little harder to manage things like this.

In React we could just use refetchQueries. We have this option in Vue as well, but it’s buggy at the moment. If I add it and assign a query to it, you can see that the view its still not updating correctly. Does this mean we should always use apollo in the component decorator? Not really. There are cases when we just want to make a request or times were we just want to update the data when the user changes url. For those cases using Apollo components is a better option.

Returning to where we left off, to fix this issue we just need to refetch our query. We can access our query from this.$apollo.queries.MyQuery, so we just need to add this.$apollo.queries.categories.refetch() to the success callback of category creation.

  public categorySuccess (result)  {
   alert(`${result.data.createCategory.title} was created successfully!`)
   this.$apollo.queries.categories.refetch()
 }

Now everything works as expected. If we try to create new categories we see how the view is updated and the pagination works.

Adding Toolbar Component

Everything is nice, but the user doesn’t have a way to log out yet, and we’d like to show the current category or thread when viewing one. We should also give the user a way to go back. What we’re going to do to fix this issue is add a Toolbar component. We can modify the component HelloWorld that was built when the project was created. We just need to rename it to Toolbar, get rid of what’s inside and create a new class:

//src/components/Toolbar.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import { Getter } from 'vuex-class'

@Component({
 props: {
   title: String,
   back: Boolean,
 },
 template: require('../templates/components/Toolbar.pug'),
})
export default class Toolbar extends Vue {
 @Getter('getLogin') public getLogin
}

This component receives two props: title which is the text to display in the bar and back, which is a boolean type we’re going to use to conditionally render the back button. We also will use our login getter to check if the user is logged in. If they aren’t, we’ll render a link to go to the login page. Finally, we need to add the methods to log out and go back.

export default class Toolbar extends Vue {
  @Getter('getLogin') public getLogin

 public logout (): void {
   localStorage.removeItem('token')
   this.$router.push('/signin')
   alert('See you soon :)')
 }

 public goBack (): void {
   this.$router.go(-1)
 }
}

The logout method just deletes the token from the localStorage, and navigates home. Notice we don’t need to set the login state here, because we’re checking that everytime we change routes. goBack uses the go method from vue-router. Now let’s create the template for our Toolbar component:

// src/templates/components/toolbar.pug
nav.menu-top
 section.container
   .links
     a.link(
       @click="goBack"
       v-if="back"
     )
       i.fas.fa-arrow-left
     p.link {{ title }}
   a.link(
     v-if="getLogin"
     @click="logout"
     title="logout"
   )
     i.fas.fa-user
   router-link.link(
     v-else
     to="/signin"
     title="Sign in"
   )
     i.fas.fa-user

We check if we should render the back button and show the user icon which will log the user out or send them to login view. Now we can add it to our layout. For that, we just need to import it into our Categories.ts file and use it in the template:

// src/pages/Categories.ts
import Toolbar from '../components/Toolbar'
@Component({
  components: {
   Toolbar,
 },
})
// src/templates/pages/categories
main.section
 Toolbar(
   title="Categories"
 )

Now we have a nice Toolbar that shows the text we want and allows us to log in or out. Let’s test log out now...and we forgot something. We aren’t conditionally showing the form to create categories.

It should be rendered only if the user is logged in. Let’s fix that by adding the login getter to src/pages/Categories.ts.

import { Getter } from 'vuex-class'
…
@Component({
  ...
})
export default class Categories extends Vue {
  @Getter('getLogin') public getLogin
   …
}

And now we can use our getter in the template

    apollo-mutation(
    ...
    v-if="getLogin"
  )

Show Threads by Category

And now everything work as expected. So far we’ve been learning one new thing after another, but we already know all the basics! Now is where it gets fun. We’re going to repeat the same pattern for every layout, adding queries or mutations to our GraphQL classes, creating templates and TypeScript files. We’ll move a lot faster now. Let’s move on to Category Show. First, let’s get back to our routes and add the necessary component:

{
  path: '/categories/:id',
  props: true,
  component: () => import('../layouts/Category')
  …
}

We’re treating Category as a layout instead of page, because it’s a parent view, and just like before, we will create a TypeScript class and a template with just a router-view:

// src/layouts/Category.ts
import Vue from 'vue'
import Component from 'vue-class-component'

@Component({
 name: 'Category',
 template: require('../templates/layouts/Category.pug'),
})
export default class Category extends Vue {
}

// src/templates/layouts/Category
router-view

The default view will be a list of Threads, so let’s create that page. First we add the route:

{
  ….
  // category object
   children: [
   {
     path: '',
     name: 'Threads',
     props: true,
     component: () => import('../pages/Threads'),
   },
     …
    ]
}

Now we just need to add the query to get a category, and the mutation to create new threads for a given category:



// src/graphql/queries.ts
public category () {
  return gql `
    query category($id: ID!) {
      category(id: $id) {
        id
        title
        threads {
          id
          title
          slug
          insertedAt
          updatedAt
        }
      }
    }`
  }

// src/graphql/mutations
// Threads
 public createThread () {
   return gql `
     mutation createThread($title: String!, $id: ID!, $body: String!) {
       createThread(title: $title, categoryId: $id, body: $body) {
         id
         title
       }
     }
   `
 }

The query receives an ID, which is almost a string, and fetches the category if it exists. We will get the category id from props, which gets the value from the url. The mutation receives three params: an ID, the thread title, and the body of the first post. What we’re going to do next is create a page called Threads.ts

//src/pages/Threads.ts
import { Vue } from 'vue-property-decorator'
import Component from 'vue-class-component'
import Queries from '../graphql/Queries'
import Toolbar from '../components/Toolbar'
import {
 Getter
} from 'vuex-class'

let Query = new Queries()

@Component({
 components: {
   Toolbar
 },
 props: {
   id: String
 },
 template: require('../templates/pages/threads.pug')
})
export default class Threads extends Vue {
 @Getter('getLogin') getLogin
 categoryQuery: any = Query.category()

 goToThread (categoryId: string, threadId: string) {
   this.$router.push(`/categories/${categoryId}/threads/${threadId}`)
 }
}

Notice that this time we’re not using apollo in our component decorator. That’s because we’re in a situation where it’s better to use the query component. We’re going to create new threads, but in another view so we don’t really need to use refetch or any other Apollo method directly.

We also define a getter to conditionally render the button to create new threads and we assign our query to the categoryQuery variable. Below we create a simple function to navigate to the desired thread,

And now in our template we add the query component. We define the query and pass the category id, we add a skip method that will be triggered if no id is found, and also notice we use fetchPolicy=”network-only”. This property decides how you want your component to interact with Apollo cache. When we define network-only we are telling Apollo that we don’t want to get data from cache for this query, we want to fetch new data every time we enter this route.

//src/templates/pages/Threads.pug
main.section
 ApolloQuery(
   :query="categoryQuery"
   :variables="{ id }"
   :skip="!id"
   fetchPolicy="network-only"
 )
   section(slot-scope="{ result: { loading, error, data } }")
     div.loading.apollo(
       v-if="loading"
     ) Loading...
     div.error.apollo(
       v-else-if="error"
     ) An error ocurred
     div.result.apollo(
       v-else-if="data"
     )
       Toolbar(
         back
         :title="data.category.title"
       )
       ul.thread-list(
         v-if="data.category.threads && data.category.threads.length > 0"
       )
         li.thread(
           @click="goToThread(data.category.id, thread.id)"
           :key="thread.id"
           v-for="thread of data.category.threads"
         )
           section.thread-side
             i.icon.fas.fa-book-open
           section.thread-main
             header.thread-header
               span {{ thread.title }}
               span.date {{ thread.insertedAt }}
             dl.thread-main-list
               dt Last Updated
               dd {{ thread.updatedAt }}
       ul.thread-list(v-else)
         li.thread-list-header
           p.subtitle There are no threads to display.
       router-link.pure-button.-fixed(
         :to="`/categories/${data.category.id}/new-thread/`"
         v-if="getLogin"
       ) New Thread

Inside the template, we add conditional divs for the different states of a request Loading, Error, Success (data). If we get data, we show the name of the category in the toolbar, and we map the threads if there are any.

Creating New Threads

Right now we don’t have any threads, so let’s add the ability to create one. We’ll start by adding the route:

{
  ….
  // category object
  children: [
    {
    path: '/categories/:id/new-thread',
    name: 'newThread',
    props: true,
    component: () => import('../pages/NewThread'),
   },
 ]
  …
}

Now we create the corresponding TypeScript file; we’ll call it NewThread.ts. We’re going to use Apollo queries, so I will define the callbacks and the reactive properties.

//src/pages/NewThread.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import Mutations from '../graphql/mutations'
import Toolbar from '../components/Toolbar'
import { Getter } from 'vuex-class'
import { Prop } from 'vue-property-decorator'

const Mutation = new Mutations()

@Component({
 name: 'newThread',
 components: {
   Toolbar,
 },
 template: require('../templates/pages/NewThread.pug'),
})
export default class NewThread extends Vue {
 @Prop()
 public id!: number
 @Getter('getLogin') public getLogin
 public createThread = Mutation.createThread()
 public title: string = ''
 public postBody: string = ''

 public newThread (mutate): void {
   if (this.title !== '' && this.postBody !== '') {
     mutate()
   } else {
     alert('Error: Empty fields')
   }
 }

 public threadSuccess (result): void {
   alert(`${this.title} was created successfully!`)
   this.$router.push(`/categories/${this.id}`)
 }

 public threadError (error): void {
   alert(error)
 }
}

This is basically more of what we’ve done before: define the variables and the getter, assign the mutation or query, then create the callbacks for success and error on thread creation. We also create a simple validation to avoid empty threads. Now we just have to create the template

//src/templates/pages/NewThread.pug
main.section
 Toolbar(
   back
   title="New Thread"
 )
 apollo-mutation(
   :mutation="createThread"
   :variables="{title, id, body: postBody}"
   @done="threadSuccess"
   @error="threadError"
 )
   section.pure-form.pure-form-stacked(
     slot-scope="{ mutate }"
   )
     fieldset
       input.pure-input-1(
         id="title"
         placeholder="Title"
         type="text"
         v-model="title"
       )
     fieldset.pure-1.thread-comment-box
       textarea.textarea(
         id="postBody"
         resize="false"
         v-model="postBody"
       )
     button.pure-button.pure-input-1(
       @click="mutate"
     ) Create Thread

Just like before, we use the Mutation component from Apollo and we assign our reactive variables to the fields with v-model. Now we can test thread creation:

And awesome, it’s working!

Formatting Dates

But now we can see a couple a small but annoying issues. First off, our dates aren’t formatted: 2018-11-08T09:31:18.986Z. Let’s fix that before going forward. We will add a dependency on Moment.

yarn add moment

Once we have moment installed, we just need to create a helper method:

// src/helpers/timeFormat.ts
import moment from 'moment'

export default class TimeFormat {

public formatDate (date: string) {
   if (date) {
     const longDate: any = moment(date)
     console.log(longDate)
     return moment(longDate['_d']).format('MM-DD-YYYY hh:mm')
   }
 }
}

We create a TimeFormat class to manage various formats, inside we define a method called formatDate, which receives a date in ISO-8601 format: 2018-11-08T09:31:18.986Z. We process it with moment and then we use its format method to return the date in the format we want. Now we just have to import this file into src/pages/Categories.ts

import TimeFormat from '../helpers/timeFormat'

export default class Threads extends Vue {
  ...
  public timeF = new TimeFormat()
  ...
}

And in our template we change:

 span.date {{ thread.insertedAt }}
 dd {{ thread.updatedAt }}

to:

span.date {{ timeF.formatDate(thread.insertedAt) }}
dd {{ timeF.formatDate(thread.updatedAt) }}

After saving, our date is correctly formatted.

Showing Posts

We’re almost done, the next step is to see posts for a given thread. It’s very similar to the layout we just did, but it has some differences. As usual, let’s start by adding the route:

{
  path: '/categories/:id/threads/:threadId',
  name: 'Thread',
  props: true,
  component: () => import('../pages/Thread'),
},

Now we need to add the query to get a specific thread and the mutation to create new posts:

// src/graphql/Queries.ts
export default class Queries {
...
// Threads
 public thread () {
   return gql`
     query thread($id: ID!) {
       thread(id: $id) {
         id
         title
         slug
         insertedAt
         updatedAt
         category {
           id
           title
         }
         posts {
           id
           body
           insertedAt
           updatedAt
           user {
             id
             name
             email
           }
         }
       }
     }
   `
  }
}

Just like category, this query just needs one param, the thread id.

// src/graphql/Mutations.ts
export default class Mutations {
...
// Post
 public createPost () {
   return gql `
     mutation createPost($body: String!, $id: ID!) {
       createPost(body: $body, threadId: $id) {
         id
       }
     }
   `
 }
}

To create a post we need two params, the body of the post, and the id of the thread. But now it gets good: we need to support markdown and show user gravatar. Luckily for us, there are vue components for both. To add them we just need to run the following command

yarn add vue-gravatar vue-markdown

Now that we have our components installed, let’s create Thread.ts inside src/pages.

/src/pages/Thread.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import Queries from '../graphql/queries'
import Toolbar from '../components/Toolbar'
import { Getter } from 'vuex-class'
import TimeFormat from '../helpers/timeFormat'
import VueMarkdown from 'vue-markdown'
import Gravatar from 'vue-gravatar'

const Query = new Queries()

@Component({
 components: {
   VueMarkdown,
   Toolbar,
   Gravatar,
 },
 name: 'Thread',
 props: {
   threadId: String,
 },
 template: require('../templates/pages/Thread.pug'),
})
export default class Thread extends Vue {
 @Getter('getLogin') public getLogin
 public threadQuery = Query.thread()
 public timeF = new TimeFormat()
}

In this file, we just import the components we need which are VueMarkdown, Toolbar and Gravatar. Just like before, we assign the query and we get the current login state. We want to show the date in each post, so we need our helper.

NOTE: Sometimes, TypeScript don’t recognize vue-gravatar and vue-markdown. We have two quick fixes for this. The first and most reliable one is to install @types/my-library, i.e. yarn add @types/vue-markdown. But there are cases where a @types package is not found. In those cases we just need to create a module. We can do that in a shim file, ie:

// src/gravatar-shims.d.ts
declare module 'vue-gravatar'

This is a little dirty and an actual typespec would be ideal, but if you’re moving fast there’s no shame.

Now we create the template:

src/templates/pages/Thread.pug
main.section
 ApolloQuery(
   :query="threadQuery"
   :variables="{ id: threadId }"
   :skip="!threadId"
   fetchPolicy="network-only"
 )
   section(slot-scope="{ result: { loading, error, data } }")
     div.loading.apollo(
       v-if="loading"
     ) Loading...
     div.error.apollo(
       v-else-if="error"
     ) An error ocurred
     div.result.apollo(
       v-else-if="data"
     )
       Toolbar(
         back
         :title="data.thread.title"
       )
       ul.thread-list.-thread(
         v-if="data.thread.posts && data.thread.posts.length > 0"
       )
         li.thread.-thread-post(
           :key="post.id"
           :id="post.id"
           v-for="post of data.thread.posts"
         )
           section.thread-side.-thread-post
             Gravatar.icon.-gravatar(
               :email="post.user.email"
               :size="60"
               default-img="mm"
             )
           section.thread-main.-post
             header.thread-header
               span.user {{ post.user.name }}
               span.date {{ timeF.formatDate(post.insertedAt) }}
             vue-markdown.markdown-style {{ post.body }}
       router-link.pure-button.-fixed(
      :to="`/categories/${data.thread.category.id}/threads/${data.thread.id}/new-post`"
         v-if="getLogin"
       ) New Post

The syntax is the same as our last view, we just add a few things, like our gravatar and markdown components.

...
Gravatar.icon.-gravatar(
  :email="post.user.email"
  :size="60"
  default-img="mm"
)

…
section.thread-main.-post
    ...
  vue-markdown.markdown-style {{ post.body }}

...

If we save our template, we see a sweet thread with markdown support:

Creating Posts

We’re almost done! The only thing left is to add the ability to create a new post for a given thread. This will be super quick, so lets do it!

Again, we add the route:

//src/router/routes.ts
// Category  children array
children: [
  {
   path: '/categories/:id/threads/:threadId/new-post',
   name: 'newPost',
   props: true,
   component: () => import('../pages/NewPost'),
 },
]

Now let’s create a NewPost.ts file, and inside continue the pattern we’ve been using throughout the lesson.

src/pages/NewPost.ts
import Vue from 'vue'
import VueMarkdown from 'vue-markdown'
import Component from 'vue-class-component'
import Mutations from '../graphql/mutations'
import { Prop } from 'vue-property-decorator'
import TimeFormat from '../helpers/timeFormat'
import Toolbar from '../components/Toolbar'
import { Getter } from 'vuex-class'

const Mutation = new Mutations()

@Component({
 name: 'NewPost',
 components: {
   Toolbar,
   VueMarkdown,
 },
 template: require('../templates/pages/NewPost.pug'),
})
export default class NewPost extends Vue {
 @Prop()
 public threadId!: string
 @Prop()
 public id!: string
 @Getter('getLogin') public getLogin
 public preview: boolean = false
 public postBody: string = ''
 public newPostMutation = Mutation.createPost()
 public timeF = new TimeFormat()

 public createPost (mutate): void {
   if (this.postBody !== '') {
     mutate()
   } else {
     alert('Error: Empty Post!')
   }
 }

 public postSuccess (result): void {
   if (result.data.createPost) {
     this.$router.push(`/categories/${this.id}/threads/${this.threadId}`)
   }
 }

 public postError (error): void {
   alert(error)
 }

 public editPost (): void {
   this.preview = false
 }

 public previewPost (): void {
   this.preview = true
 }
}

Everything is pretty standard here. We initialize our props and assign the mutation to create a post, then we define success and error callbacks for post creation.

Using a Tab Panel to Conditionally Render Editor and Markdown Preview

To manage the change between editor and preview, we define a preview boolean variable. We make it false by default. The idea is to show the editor if it’s false and show the preview if it’s true. We define a couple of methods that will alter the value of the variable on click. Now we create the template:

src/templates/pages/NewPost.pug
main.section.new-post(
 v-if="getLogin"
)
 Toolbar(
   back
   title="New Post"
 )
 ul.tab-panel
   li.tab(
     :class="!preview && 'active'"
     @click="editPost()"
   )
     h3 Editor
   li.tab(
     :class="preview && 'active'"
     @click="previewPost()"
   )
     h3 Preview
 section.thread-comment-box(v-if="!preview")
   textarea.textarea(
     id="postBody"
     placeholder="Please be friendly..."
     v-model="postBody"
   )
   ApolloMutation(
     :mutation="newPostMutation"
     :variables="{ body: postBody, id: threadId }"
     @done="postSuccess"
     @error="postError"
   )
     article(slot-scope="{ mutate }")
       button.pure-button.pure-u-1(
         @click="createPost(mutate)"
       ) New Post
 vue-markdown.markdown-style(v-else) {{ postBody }}

The new stuff here is the tab panel which basically changes the value of the variable preview we defined before. We use v-if and v-else to conditionally render the editor and the preview container, everything else follows the same pattern as before. Now let’s test our new view:

And we’re finished! We just built a complete forum with Vue, TypeScript and GraphQL, congratulations!

Summary

This was a long lesson, but the reward was totally worth the time. We learned:

How to generate a Vue Project How to configure TypeScript in a Vue project How to separate your styles from the templates and logic in a vue project. How to separate you templates from your logic. What is and how to use Pug What is and how to use Apollo What is and how to use TypeScript What is and how to use Vue.js What is and how to use classes decorators How to use Queries How to use Mutations How to use Query component How to use Mutation component, How to use and configure Vuex How to use and configure Vue-router How to debug common issues in a Vue + TypeScript + Apollo project And more

I hope you enjoyed it, see you soon!