├── .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 | 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 | --------------------------------------------------------------------------------