新入职的工程师使用Vue+GraphQL+无服务器开发了一个待办事项应用

首先

关于这篇文章

    1. 新入社員工程师尝试使用完全没有接触过的技术创建了一个Todo应用的故事

 

    1. 略带教程的格式

 

    1. 使用了命令行界面等简便的构建方法

 

    1. 对于各种技术的解释较少

 

    1. 适合想要了解vue和GraphQL风格的人?

 

    欢迎提出错误指正或者更高效的写作方法,以及未来的学习建议。

所有事项应用程序概述

重要文件

    • ユーザーの認証が出来ること

 

    TodoのCRUD操作が出来ること

技术

    • Vue.js

 

    • graphcool(serverless GraphQL)

 

    Apollo(クライアント側でGraphQLを扱いやすくするためのもの)

开发环境

    • OS: Ubuntu 16.04.3 LTS

 

    • node: v8.1.4

 

    • npm: 5.5.1

 

    docker: 17.0.5.0-ce

创建一个任务管理应用

产品已经完成了。

Graphcool的准备。

首先,我們會從建立GraphQL的環境開始準備。

mkdir todo
cd todo
npm install -g graphcool
graphcool init graphcool

现在创建了一个名为graphcool的文件夹,并且准备工作完成了。非常简单!

准备Vue

接下来,我们会准备Vue的环境。
我们会顺便将以后会使用到的工具也安装进去。

npm install -g vue-cli

# 色々質問されますが今回私は基本的にそのままで、テスト関係だけnoにしました
vue init webpack vue
cd vue
npm install
npm install --save vue-apollo apollo-client-preset apollo-link-context graphql graphql-tag

现在,一个名为Vue的文件夹已经被创建,准备工作完成了。非常简单!!

好的,那么现在首先我们从数据库这一方面开始建立吧。

Graphcool的实现

创建模式。

在这个Todo应用中,用户认证是要求之一。Graphcool提供了用于进行认证的模板,因此可以轻松实现认证功能。

cd graphcool
graphcool add-template graphcooltemplates/auth/email-password

这样就添加了认证所需的内容。太厉害了!!!
(顺便提一下,在Graphcool的网站教程中也介绍了使用Facebook登录的方法。)

然而,追加后无法立即使用该功能。
要使用该功能,需要取消graphcool.yml和types.graphql的注释。
在取消注释的同时,还会进行Todo应用的表格设计。

types: ./types.graphql

functions:
  signup:
    type: resolver
    schema: src/email-password/signup.graphql
    handler:
      code: src/email-password/signup.ts

  authenticate:
    type: resolver
    schema: src/email-password/authenticate.graphql
    handler:
      code: src/email-password/authenticate.ts

  loggedInUser:
    type: resolver
    schema: src/email-password/loggedInUser.graphql
    handler:
      code: src/email-password/loggedInUser.ts

permissions:
  - operation: "*"

只需要取消掉graphcool.yml文件中的注释。
然后就可以使用认证功能了。

type User @model {
  id: ID! @isUnique
  createdAt: DateTime!
  updatedAt: DateTime!

  email: String! @isUnique
  password: String!
  todos: [Todo!]! @relation(name: "UserTodos")
}


type Todo @model {
  id: ID! @isUnique
  createdAt: DateTime!
  updatedAt: DateTime!

  title: String!
  done: Boolean!
  author: User! @relation(name: "UserTodos")
}

在types.graphql中进行模式定义。
User对象具有系统字段email、password和todos。
Todo对象具有系统字段title、done和author。
title表示todo的名称,done表示该todo是否完成,author表示todo的所有者。
这与基本的Todo应用程序的表设计是相同的。
顺便提一下,!表示该项为必填项(非空)。

建立数据库

好的,既然数据库模式定义已经完成了,那我们就来搭建数据库吧。
这次我们将在Docker上创建。
虽然我对Docker并不太熟悉,但是我成功地使用它了。
当遇到困难时,可以使用的奇门遁甲是“删除所有数据卷”。

graphcool local up

# Please choose the cluster you want to deploy toと
# 聞かれるので、一番下のlocalを選択。その後はそのままでok
graphcool deploy

如果成功了,我认为会显示各种内容。
然后我想在底部会有一个名为”Simple API:”的部分,我会记住它的URL。
这在连接到vue时是必需的。
如果忘记记下来了,可以通过graphcool info之类的方式进行确认。

好了,现在可以使用GraphQL了!太棒了!

创建用户

在Todo应用程序中,我们没有创建用户注册功能。
因此,我们需要从其他地方注册用户。请键入以下命令!

graphcool playground

然后会打开如下的屏幕。
你可以直接从这里开始使用GraphQL。
(顺便提一下,你还可以通过右上角的齿轮标志选择VIM模式。)

graphcool1.png

我想要创建一个用户。
由于这次添加了认证功能,所以我们将使用名为`sigupUser`的mutations。
mutations是指除了查询以外的操作,如更新、删除和创建(我认为)。
如果您能看一下右侧显示的SCHEMA的MUTATIONS部分,大致可以了解其中的情况。

请按照以下方式操作,点击中间的执行按钮。

mutation {
  signupUser(email:"test@mail.com" password:"test")
  {
    id
    token
  }
}
graphcool2.png

用中文本地化解释以下内容,只需要一种选项:
signupUser()是使用的API名称,()内是参数。
{}内返回结果。
在本例中,创建用户后返回id和token。
用户已成功创建。

好的,现在让我们来看一下刚刚创建的用户信息。
复制id,然后写下以下的查询。

query {
  User(id:"cjatrk2ti02eb0189w0zgclot")
  {
    id
    createdAt
    updatedAt
    email
    password
    todos{
      id
      title
    }
  }
}
graphcool3.png

做得很好!太棒了!

Vue的实现

好了,接下来我们终于要进行Vue端的实现了。
关于Vue,因为在工作中接触过Angular,所以我就想着它们的感觉应该相似吧,结果我没有很仔细地看教程就开始使用,结果踩了不少坑。
我知道可能有一些不好的实现方法,请多多包涵。

登录功能

首先需要登录。
修改文件是 vue/src/main.js 和 vue/App.vue。
添加的文件是 vue/src/component 目录下的 Login.js 和 Logout.js,以及 vue/src/constants 目录下的 graphql.js 和 settings.js。

首先查看App.vue。
这是所谓的索引页面。
由于样式被直接使用,这里省略了它。

<template>
  <div id="app">
    <logout v-if="isLoggedIn"></logout>
    <login v-else></login>

    <router-view/>
  </div>
</template>

<script>
  import Logout from './components/Logout'
  import Login from './components/Login'

  export default {
    name: 'app',
    components: {
      Logout,
      Login
    },
    computed: {
      isLoggedIn () {
        return this.$root.$data.token
      }
    }
  }
</script>

只是加载和显示登录和退出组件。
根据isLoggedIn()函数中存储的令牌来切换显示。

<template>
  <button @click="logout()">Logout</button>
</template>

<script>
  import {AUTH_TOKEN} from '../constants/settings'

  export default {
    name: 'Logout',
    methods: {
      logout () {
        localStorage.removeItem(AUTH_TOKEN)
        this.$root.$data.token = localStorage.getItem(AUTH_TOKEN)
        this.$router.push({path: '/'})
      }
    }
  }
</script>

接下来是Logout.vue,也是非常简单的,有一个登出按钮,当按下时,会删除保存在本地存储中的令牌信息,并将页面重定向到顶部。

<template>
  <div>
    <input v-model="email" type="text" placeholder="Email">
    <input v-model="password" type="password" placeholder="Password">
    <button @click="confirm()">Login</button>
  </div>
</template>

<script>
  import {AUTH_TOKEN} from '../constants/settings'
  import {LOGIN_MUTATION} from '../constants/graphql'

  export default {
    name: 'Login',
    data () {
      return {
        email: '',
        password: ''
      }
    },
    methods: {
      confirm () {
        const {email, password} = this.$data

        this.$apollo.mutate({
          mutation: LOGIN_MUTATION,
          variables: {
            email,
            password
          }
        }).then((result) => {
          const token = result.data.authenticateUser.token
          localStorage.setItem(AUTH_TOKEN, token)
          this.$root.$data.token = localStorage.getItem(AUTH_TOKEN)

          this.$router.push({path: '/todos'})
        }).catch((error) => {
          alert(error)
        })
      }
    }
  }
</script>

请先登录。
以email和password作为模板,请您填写相应的表单并点击发送按钮。
表单中输入的值将与data()中的email和password进行绑定。

有一种方法是在用户按下发送按钮时使用confirm()函数进行身份验证。
this.$apollo.mutate用于调用GraphQL的mutation。
该mutation实际上是在vue/src/constants/graphql.js中定义的以下内容。

export const LOGIN_MUTATION = gql`
  mutation LoginMutation($email: String!, $password: String!) {
    authenticateUser(email: $email, password: $password) {
      id
      token
    }
  }
`

如果身份验证成功,将返回ID和令牌。然后将令牌保存到本地存储,然后跳转到待办事项页面。

好的,现在关于保存的token的使用位置是在vue/src/main.js文件中。

import {AUTH_TOKEN, DB_URL} from './constants/settings'
import {ApolloClient} from 'apollo-client'
import {HttpLink} from 'apollo-link-http'
import {setContext} from 'apollo-link-context'
import {InMemoryCache} from 'apollo-cache-inmemory'
import Vue from 'vue'
import App from './App'
import router from './router'
import VueApollo from 'vue-apollo'

Vue.config.productionTip = false
Vue.use(VueApollo)
let token

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

const httpLink = new HttpLink({
  uri: DB_URL
})

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

const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  defaultOptions: {
    $loadingKey: 'loading'
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  apolloProvider,
  router,
  data: {
    token
  },
  template: '<App/>',
  components: { App }
})

在ApolloClient中,我们使用authLink来在请求头中添加token。同时,在httpLink中指定了我们自己搭建的DB的Simple API的url。我们将这两个链接作为ApolloClient的link进行传递。

现在,我们可以实现以下的登录和登出功能。

Screenshot-2017-12-5 vue(1).png
Screenshot-2017-12-5 vue(2).png

设计上一点都不起眼,真是令人感到寂寞。真是可惜。

待办事项功能

终于到了正题,即Todo功能。

以下是中文的同义句:

主要是在”vue/src/components”目录下的”Todos.vue”、”Todo.vue”和”CreateTodo.vue”文件中。
基本上,在”Todos.vue”文件中进行数据获取,在其子组件”Todo.vue”和”CreateTodo.vue”中进行数据库更新操作。

首先,我们来看一下子组件的Todo.vue。

<template>
  <div>
    <input type="checkbox" @click="done" :checked="todo.done">
    <input v-model="title">
    <button @click="edit">編集</button>
    <button @click="del">削除</button>
  </div>
</template>

<script>
  import {UPDATE_TODO, DELETE_TODO} from '../constants/graphql'

  export default {
    name: 'Todo',
    props: ['todo'],
    data () {
      return {
        title: this.todo.title
      }
    },
    methods: {
      done () {
        this.$apollo.mutate({
          mutation: UPDATE_TODO,
          variables: {
            id: this.todo.id,
            done: !this.todo.done
          }
        })
      },
      edit () {
        this.$apollo.mutate({
          mutation: UPDATE_TODO,
          variables: {
            id: this.todo.id,
            title: this.title
          }
        })
      },
      del () {
        this.$apollo.mutate({
          mutation: DELETE_TODO,
          variables: {
            id: this.todo.id
          }
        }).then(() => {
          this.$emit('refresh')
        })
      }
    }
  }
</script>

虽然有点长,但内容非常简单。
父组件会传递一行的Todo数据。
模板中会显示该数据,并设置处理用的按钮。
方法中的done、edit和del会更新数据库。
当点击done时,会将任务标记为已完成(或未完成)进行更新,当点击edit时,会修改标题。
此外,这是关于父组件的事情,当触发del的this.$emimt(‘refresh’)时,会重新获取数据。
本来应该是改变列表而不是重新获取数据,但由于实现方法有点模糊,所以只能采取这种形式。
顺便提一下,对于done和edit,即使不触发事件,它们也会自动变化。
但是我不知道为什么会有这样的操作。
请有人告诉我!!

好的,接下来是父组件的Todos.vue。

<template>
  <div>
    <h4 v-if="loading">Loading Todos...</h4>
    <div v-else>
      <h3>タスク追加</h3>
      <create-todo @refresh="resetStore"></create-todo>

      <h3>未完了タスク</h3>
      <todo v-for="(todo, i) in tasks" @refresh="resetStore" :key="todo.id" :todo="todo"></todo>

      <h3>完了タスク</h3>
      <todo v-for="(todo, i) in done" @refresh="resetStore" :key="todo.id" :todo="todo"></todo>
    </div>
  </div>
</template>

<script>
  import {USER_TODOS, LOGGED_IN_USER} from '../constants/graphql'
  import Todo from './Todo'
  import CreateTodo from './CreateTodo'

  export default {
    name: 'Todos',
    data () {
      return {
        todos: [],
        user: '',
        loading: 0
      }
    },
    components: {
      Todo,
      CreateTodo
    },
    computed: {
      done () {
        return this.todos.filter(todo => todo.done === true)
      },
      tasks () {
        return this.todos.filter(todo => todo.done === false)
      }
    },
    apollo: {
      user: {
        query: LOGGED_IN_USER,
        update (data) {
          return data.loggedInUser.id
        }
      },
      todos: {
        query: USER_TODOS,
        variables () {
          return {
            id: this.user
          }
        },
        update (data) {
          return data.User.todos
        }
      }
    },
    methods: {
      resetStore () {
        this.$apollo.provider.defaultClient.resetStore()
      }
    }
  }
</script>

在模板中,进行新注册栏的显示和待办事项列表的显示。
通过计算,将已完成任务和未完成任务分开,然后分别显示它们。

在这里,我使用 Apollo 来执行 GraphQL 的查询。
用户根据填充在标头中的令牌,返回当前用户的查询。
在 vue/src/main.js 中填充的令牌在这里被使用。
(顺便提一下,在 graphcool 的 playground 中使用这个查询(LoggedInUser)时,需要从左下角的 HTTP HEADERS(0) 传递 authorization 和 Bearer 令牌)。

在Todos中,我们获取当前用户的待办事项列表。
我们将变量设置为反应性的,以便在用户更改时执行。
之所以这样做是因为我们不知道如何在获取用户后再获取Todos。
实际上,当执行Todos时会报错说”用户不存在!”。

来吧,到了这一步,只需要按照相同的方法实现vue/src/components/CreateTodo.vue,并将todos添加到router中,就完成了。
以下是完成的Todo应用程序!太棒了!

Screenshot-2017-12-5 vue(3).png

因为没有指定风格,所以还是感觉寂寞。但是终于完成了!!!

最后

感谢您一直以来的陪伴。
如果我能为您提供任何帮助,我会非常高兴。

作为尝试制作的感想,我发现很难找到apollo的信息,有些困难,同时在vue中对数组操作也无法如我所愿,有些困难。再加上本来就缺乏web开发知识,对于令牌的使用等方面完全不了解。总的来说,我很享受接触到以前没有接触过的技术!

嗯,因为我是新人,所以大部分技术我都没有接触过!虽然我不确定是否要写成一篇文章,但下一步我想将此次制作的东西部署到AWS上试试看。

好的好的。

请给我一个在中国进行本土化的选项。

GraphQL入门-让你爱上GraphQL
Vue + Apollo教程-简介(注意信息有点陈旧)
Vue
Graphcool文档
vue-apollo

广告
将在 10 秒后关闭
bannerAds