Simple ToDo GraphQL API in VueJS & TailwindCSS with Docker [PART 03]
Tags: vue, graphql, tailwindcss, docker
Published: 2020-08-14 13:51:17

In this article, we will create VueJS app of ToDo App with TailwindCSS using GraphQL API we built in previous articles.

Demo at:

{% youtube eZhmY9COGkQ %}

Code Repo is at:

{% github sulmanweb/todo-app %}

In the previous article, we created rails, graphql API with MongoDB:

{% link sulmanweb/simple-todo-graphql-api-in-ruby-on-rails-and-mongodb-with-docker-part-02-3d2f %}

Now we move to VueJS part of the project. So, first create the VueJS app with the following command in the `front-vue` directory:

```bash
docker run --rm -v "${PWD}:/$(basename `pwd`)" -w "/$(basename `pwd`)" -it node:14.5-alpine sh -c "yarn global add @vue/cli && vue create . && vue add apollo"
```

This command will create VueJS app in the directory and add plugin of `vue-apollo` which we will use for the graphql api backend access.

Then create the `front-vue` service in docker and start in docker by following commands:

`todo-app/docker-compose.yml`
```yaml
	front-vue:
	    build: ./front-vue
	    ports:
	      - "8080:8080" # use port that you want to in your local instead of 8091
	    volumes:
	      - ./front-vue:/front-vue
	      - front_node_modules:/front-vue/node_modules
volumes:
	front_node_modules:
```

`todo-app/front-vue/Dockerfile`
```Dockerfile
FROM node:14.5-alpine
WORKDIR /front-vue
COPY package*.json ./
RUN yarn install
COPY . .
CMD ["yarn", "serve"]
```

```bash
docker-compose up -d --build
```

# Init Project:
We will use the following packages which you can see `todo-app/front-vue/package.json`
- [Font Awesome SVG Icon package for vue](https://www.npmjs.com/package/@fortawesome/vue-fontawesome)
- [TailwindCSS](https://www.npmjs.com/package/tailwindcss)
- [PurgeCSS for tailwind unused css purging](https://www.npmjs.com/package/@fullhuman/postcss-purgecss)
- [Vue-Apollo (which we added as vue plugin)](https://apollo.vuejs.org/)

Initialize [tailwindcss](https://tailwindcss.com/) and [fontawesome](https://fontawesome.com/) in main.js by adding following code:

`todo-app/front-vue/src/main.js`
```js
import { library } from "@fortawesome/fontawesome-svg-core";
import {
  faPlus,
  faLongArrowAltLeft,
  faCheckCircle,
  faCircle,
  faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";

library.add(faPlus, faLongArrowAltLeft, faCheckCircle, faCircle, faTrash);

Vue.component("font-awesome-icon", FontAwesomeIcon);

import "./assets/styles/index.css";
```

Clear the `App.js` and other files for code and styling. Also add purging css for production:
`todo-app/front-vue/postcss.config.js`
```js
const autoprefixer = require("autoprefixer");
const tailwindcss = require("tailwindcss");
const postcssPurgecss = require(`@fullhuman/postcss-purgecss`);

const purgecss = postcssPurgecss({
  // Specify the paths to all of the template files in your project.
  content: ["./public/**/*.html", "./src/**/*.vue"],
  // Include any special characters you're using in this regular expression.
  // See: https://tailwindcss.com/docs/controlling-file-size/#understanding-the-regex
  defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
  // Whitelist auto generated classes for transitions and router links.
  // From: https://github.com/ky-is/vue-cli-plugin-tailwind
  whitelistPatterns: [
    /-(leave|enter|appear)(|-(to|from|active))$/,
    /^(?!(|.*?:)cursor-move).+-move$/,
    /^router-link(|-exact)-active$/,
    /svg.*/,
    /fa.*/,
  ],
});

module.exports = {
  plugins: [
    tailwindcss,
    autoprefixer,
    ...(process.env.NODE_ENV === "production" ? [purgecss] : []),
  ],
};
```

Also, add tailwind CSS file
`todo-app/front-vue/src/assets/styles/index.css`
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Change the name of token and URL of backend graphql
`todo-app/front-vue/src/vue-apollo.js`
```js
// Name of the localStorage item
const AUTH_TOKEN = "token";

// Http endpoint
const httpEndpoint =
  process.env.VUE_APP_GRAPHQL_HTTP || "http://localhost:3000/graphql";
```

# GraphQL using Vue-Apollo
There are many ways to make graphql mutation and query through Vue-Apollo which I have explained in different components and views but the main way which I used everywhere and I like most is making querying and mutation in `.gql` files separately and then we can change the query or mutation even after project is created separately in their own files. Also the way the bearer authorization being handled by the vue-apollo automatically is great.

So, we create a folder `graphql` in `src` and we add all the queries and mutations in gql language there. For example:

`todo-app/front-vue/src/graphql/mutations/signUp.gql`
```gql
mutation signUp($email: String!, $password: String!) {
  signUp(input: { email: $email, password: $password }) {
    token
  }
}
```

`todo-app/front-vue/src/graphql/queries/me.gql`
```gql
query me {
  me {
    lists {
      id
      name
    }
  }
}
```

# Different ways to make query and mutations
## Mutations
We can use mutation as components with a template like in signUp:

`todo-app/front-vue/src/views/auth/signUp.vue`
```vue
<ApolloMutation
 :mutation="require('@/graphql/mutations/signUp.gql')"
 :variables="{ email, password }"
 @done="signedUp"
 >
  <template v-slot="{ mutate, loading, error }">
    <!-- Error -->
    <div class="bg-red-100 border-l-4 border-red-400 p-4" v-if="error">
      <div class="flex">
        <div class="flex-shrink-0">
          <svg
            class="h-5 w-5 text-red-400"
            viewBox="0 0 20 20"
            fill="currentColor"
          >
          <path
            fill-rule="evenodd"
            d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
            clip-rule="evenodd"
          />
          </svg>
        </div>
        <div class="ml-3">
          <p class="text-sm leading-5 text-red-700">
            {{ error.toString().replace("Error: GraphQL error: ", "") }}
          </p>
        </div>
      </div>
    </div>
    <!-- Sign Up Form -->
    <form action="#" class="mt-5" @submit.prevent="mutate()">
      <!-- Email -->
      <div class="mb-5">
        <input
          type="email"
          name="email"
          id="email"
          v-model="email"
          class="rounded w-full shadow border border-gray-900 tracking-wide text-xl p-2"
          placeholder="Email"
          autocomplete="on"
          required
        />
      </div>

      <!-- Password -->
      <div class="mb-5">
        <input
          type="password"
          name="password"
          v-model="password"
          class="rounded w-full shadow border border-gray-900 tracking-wide text-xl p-2"
          placeholder="Password"
          id="sign-up-password"
          autocomplete="off"
        />
      </div>

      <div class="text-center flex justify-between">
        <!-- sign in link -->
        <router-link
          to="/sign_in"
          class="self-center hover:underline hover:text-gray-800"
          >Sign In</router-link
        >
        <!-- sign up link -->
        <button
          type="submit"
          :disabled="loading"
          class="px-4 py-2 rounded tracking-wide font-semibold bg-black text-white text-lg hover:bg-gray-800 shadow-lg"
        >
          Sign Up
        </button>
      </div>
    </form>
  </template>
</ApolloMutation>
```

Second way is to use in normal vue method like below:

`todo-app/front-vue/src/views/listShow.vue`
```javascript
async changeStatus(task) {
  const result = await this.$apollo.mutate({
    mutation: require("@/graphql/mutations/changeTaskStatus.gql"),
    variables: {
      id: task.id,
    },
  });
  if (!!result.data) {
    await this.loadList();
  }
},
```

## Queries
The first way is graphql query as a component like we saw in mutations.

Second way is the same to call in function same as that of mutations like:

`todo-app/front-vue/src/views/listShow.vue`
```javascript
async getList() {
  return await this.$apollo.query({
    query: require("@/graphql/queries/showList.gql"),
    variables: {
      id: this.$route.params.listId,
    },
    fetchPolicy: "network-only",
  });
},
```

Third way is to call query in apollo object in the js section of the vue component like:

`todo-app/front-vue/src/views/Home.vue`
```javascript
apollo: {
  me: {
    query: require("@/graphql/queries/me.gql"),
    result({ data }) {
      if (data) {
        this.lists = data.me.lists;
        this.loading = false;
      }
    },
  },
},
```


# See the Complete Development in Videos

{% youtube ZqCj_iOKDWU %}

{% youtube 7ZiPI1Kzt9Q %}

{% youtube lo4TbnS3pJ0 %}

{% youtube C2SBNva6GdA %}

Happy Coding!