├── .babelrc
├── .gitignore
├── Graphcool.schema
├── README.md
├── apollo.js
├── index.html
├── package.json
├── src
├── App.vue
└── main.js
├── store.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | yarn-error.log
6 |
--------------------------------------------------------------------------------
/Graphcool.schema:
--------------------------------------------------------------------------------
1 | type Color implements Node {
2 | createdAt: DateTime!
3 | fruits: [Fruit!]! @relation(name: "FruitOnColor")
4 | hex: String!
5 | id: ID! @isUnique
6 | name: String!
7 | updatedAt: DateTime!
8 | }
9 |
10 | type File implements Node {
11 | contentType: String!
12 | createdAt: DateTime!
13 | id: ID! @isUnique
14 | name: String!
15 | secret: String! @isUnique
16 | size: Int!
17 | updatedAt: DateTime!
18 | url: String! @isUnique
19 | }
20 |
21 | type Fruit implements Node {
22 | color: Color @relation(name: "FruitOnColor")
23 | createdAt: DateTime!
24 | id: ID! @isUnique
25 | name: String!
26 | updatedAt: DateTime!
27 | }
28 |
29 | type User implements Node {
30 | createdAt: DateTime!
31 | id: ID! @isUnique
32 | updatedAt: DateTime!
33 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # apollo-vuex-example
2 |
3 | An example Vue project based on the Vue template `webpack-simple`. It uses Vuex and vanilla Apollo Client.
4 |
5 | I wrote this since everyone seems to be using `vue-apollo` which encourages to put the GraphQL queries in Vue componentes. **This is an antipattern and should never be done**. The whole point of using Vuex is to centralize state and decouple your data layer code from your components.
6 |
7 | I've included the [Graphcool](https://www.graph.cool) schema so you can replicate this. Just copy pasta the schema into the Graphcool console, add some data, and then paste the simple API and subscription endpoints in `apollo.js`.
8 |
9 | ## Build Setup
10 |
11 | ``` bash
12 | # install dependencies
13 | npm install
14 |
15 | # serve with hot reload at localhost:8080
16 | npm run dev
17 | ```
18 |
--------------------------------------------------------------------------------
/apollo.js:
--------------------------------------------------------------------------------
1 | import ApolloClient, { createNetworkInterface } from 'apollo-client';
2 | import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws'
3 |
4 | // YOUR_GRAPH_QL_ENDPOINT_HERE
5 |
6 | const wsClient = new SubscriptionClient('wss://subscriptions.graph.cool/v1/cj3xgn6d2idze0104n3mpq4le', {
7 | reconnect: true,
8 | });
9 |
10 | const networkInterface = createNetworkInterface({
11 | uri: 'https://api.graph.cool/simple/v1/cj3xgn6d2idze0104n3mpq4le'
12 | });
13 |
14 | const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
15 | networkInterface,
16 | wsClient
17 | )
18 |
19 | const client = new ApolloClient({
20 | networkInterface: networkInterfaceWithSubscriptions,
21 | dataIdFromObject: o => o.id
22 | });
23 |
24 | export default client;
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | apollo-vuex-example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apollo-vuex-example",
3 | "description": "A Vue.js project",
4 | "version": "1.0.0",
5 | "author": "Pier Bover ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
9 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
10 | },
11 | "dependencies": {
12 | "apollo-client": "^1.4.2",
13 | "subscriptions-transport-ws": "^0.7.3",
14 | "vue": "^2.3.3",
15 | "vuex": "^2.3.1"
16 | },
17 | "devDependencies": {
18 | "babel-core": "^6.0.0",
19 | "babel-loader": "^6.0.0",
20 | "babel-preset-env": "^1.5.1",
21 | "cross-env": "^3.0.0",
22 | "css-loader": "^0.25.0",
23 | "file-loader": "^0.9.0",
24 | "vue-loader": "^12.1.0",
25 | "vue-template-compiler": "^2.3.3",
26 | "webpack": "^2.6.1",
27 | "webpack-dev-server": "^2.4.5"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Some fruits
4 |
5 |
6 | - {{fruit.name}} {{fruit.color.name}}
7 |
8 |
9 |
10 |
11 |
45 |
46 |
52 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import store from '../store.js';
4 |
5 | new Vue({
6 | store,
7 | el: '#app',
8 | render: h => h(App)
9 | })
10 |
--------------------------------------------------------------------------------
/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import apolloClient from './apollo.js';
4 |
5 | import gql from 'graphql-tag';
6 |
7 | Vue.use(Vuex);
8 |
9 | // apolloClient.subscribe returns an Observable instance
10 | // I've put the observer and observable here for simplicity but this should go into its own module
11 | const fruitsSubscriptionObservable = apolloClient.subscribe({
12 | query: gql`
13 | subscription {
14 | Fruit {
15 | mutation
16 | node {
17 | id
18 | name
19 | color {
20 | name
21 | }
22 | }
23 | previousValues {
24 | id
25 | }
26 | }
27 | }
28 | `,
29 | });
30 |
31 | let fruitsSubscriptionObserver;
32 |
33 | const store = new Vuex.Store({
34 | state: {
35 | fruits: {},
36 | },
37 | mutations: {
38 | SET_FRUITS(state, fruits){
39 | // having an object instead of an array makes the other methods easier
40 | // since we can use Vue.set() and Vue.delete()
41 | const object = {};
42 | fruits.map((fruit) => {
43 | object[fruit.id] = fruit;
44 | });
45 | state.fruits = object;
46 | },
47 | ADD_FRUIT(state, fruit){
48 | Vue.set(state.fruits, fruit.id, fruit);
49 | },
50 | UPDATE_FRUIT(state, fruit){
51 | Vue.set(state.fruits, fruit.id, fruit);
52 | },
53 | DELETE_FRUIT(state, fruit){
54 | Vue.delete(state.fruits, fruit.id);
55 | },
56 | },
57 | actions: {
58 | getFruits(context){
59 | apolloClient.query({
60 | query: gql`
61 | {
62 | allFruits {
63 | id
64 | name
65 | color {
66 | name
67 | }
68 | }
69 | }
70 | `
71 | }).then((result) => {
72 | context.commit('SET_FRUITS', result.data.allFruits);
73 | });
74 | },
75 | // You call this action to start the sunscription
76 | subscribeToFruits(context){
77 | fruitsSubscriptionObserver = fruitsSubscriptionObserver.subscribe({
78 | next(data){
79 | // mutation will say the type of GraphQL mutation `CREATED`, `UPDATED` or `DELETED`
80 | console.log(data.Fruit.mutation);
81 | // node is the actual data of the result of the GraphQL mutation
82 | console.log(data.Fruit);
83 | // then call your store mutation as usual
84 | switch (data.Fruit.mutation) {
85 | case 'CREATED':
86 | context.commit('ADD_FRUIT', data.Fruit.node);
87 | break;
88 | case 'UPDATED':
89 | context.commit('UPDATE_FRUIT', data.Fruit.node);
90 | break;
91 | case 'DELETED':
92 | context.commit('DELETE_FRUIT', data.Fruit.previousValues);
93 | break;
94 | }
95 | },
96 | error(error){
97 | console.log(error);
98 | }
99 | });
100 | },
101 | // You call this action to stop the subscription
102 | unsubscribeFromFruits(context){
103 | if (fruitsSubscriptionObserver) {
104 | fruitsSubscriptionObserver.unsubscribe();
105 | fruitsSubscriptionObserver = null;
106 | }
107 | },
108 | }
109 | });
110 |
111 | export default store;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | entry: './src/main.js',
6 | output: {
7 | path: path.resolve(__dirname, './dist'),
8 | publicPath: '/dist/',
9 | filename: 'build.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.vue$/,
15 | loader: 'vue-loader',
16 | options: {
17 | loaders: {
18 | }
19 | // other vue-loader options go here
20 | }
21 | },
22 | {
23 | test: /\.js$/,
24 | loader: 'babel-loader',
25 | exclude: /node_modules/
26 | },
27 | {
28 | test: /\.(png|jpg|gif|svg)$/,
29 | loader: 'file-loader',
30 | options: {
31 | name: '[name].[ext]?[hash]'
32 | }
33 | }
34 | ]
35 | },
36 | resolve: {
37 | alias: {
38 | 'vue$': 'vue/dist/vue.esm.js'
39 | }
40 | },
41 | devServer: {
42 | historyApiFallback: true,
43 | noInfo: true
44 | },
45 | performance: {
46 | hints: false
47 | },
48 | devtool: '#eval-source-map'
49 | }
50 |
51 | if (process.env.NODE_ENV === 'production') {
52 | module.exports.devtool = '#source-map'
53 | // http://vue-loader.vuejs.org/en/workflow/production.html
54 | module.exports.plugins = (module.exports.plugins || []).concat([
55 | new webpack.DefinePlugin({
56 | 'process.env': {
57 | NODE_ENV: '"production"'
58 | }
59 | }),
60 | new webpack.optimize.UglifyJsPlugin({
61 | sourceMap: true,
62 | compress: {
63 | warnings: false
64 | }
65 | }),
66 | new webpack.LoaderOptionsPlugin({
67 | minimize: true
68 | })
69 | ])
70 | }
71 |
--------------------------------------------------------------------------------