├── .browserslistrc ├── .gitignore ├── README.md ├── apollo-server ├── context.js ├── directives.js ├── mocks.js ├── resolvers.js ├── schema.graphql ├── type-defs.js └── utils │ └── db.js ├── apollo.config.js ├── babel.config.js ├── live └── db.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── icons │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest └── index.html ├── src ├── App.vue ├── assets │ ├── logo.png │ └── vue-graphql.png ├── components │ └── VueHero.vue ├── main.js └── vue-apollo.js ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue + GraphQL + Apollo fullstack example 2 | 3 | This is a demo application combining Apollo Server as a GraphQL API and a Vue application with Apollo Client on a frontend side. To start a GraphQL server, please run: 4 | 5 | ```bash 6 | npm run apollo 7 | # OR 8 | yarn apollo 9 | ``` 10 | 11 | The server will be running on `localhost:4000/graphql` and you can find a Prisma playground on this URL as well to check the schema & test queries. 12 | 13 | To start the frontend app you should run 14 | 15 | ```bash 16 | npm run serve 17 | # OR 18 | yarn serve 19 | ``` 20 | 21 | The application will be running on `localhost:8080`. 22 | -------------------------------------------------------------------------------- /apollo-server/context.js: -------------------------------------------------------------------------------- 1 | import { db } from './utils/db'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | export default ({ req, connection }) => { 5 | return { 6 | db, 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /apollo-server/directives.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Schema directives 3 | // https://www.apollographql.com/docs/graphql-tools/schema-directives.html 4 | } 5 | -------------------------------------------------------------------------------- /apollo-server/mocks.js: -------------------------------------------------------------------------------- 1 | // Enable mocking in vue.config.js with `"pluginOptions": { "enableMocks": true }` 2 | // Customize mocking: https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks 3 | export default { 4 | // Mock resolvers here 5 | } 6 | -------------------------------------------------------------------------------- /apollo-server/resolvers.js: -------------------------------------------------------------------------------- 1 | import shortid from 'shortid'; 2 | 3 | export default { 4 | Query: { 5 | allHeroes: (root, args, { db }) => db.get('heroes').value(), 6 | getHero: (root, { name }, { db }) => 7 | db 8 | .get('heroes') 9 | .find({ name }) 10 | .value(), 11 | }, 12 | 13 | Mutation: { 14 | addHero: (root, { hero }, { pubsub, db }) => { 15 | const newHero = { 16 | id: shortid.generate(), 17 | name: hero.name, 18 | image: hero.image || '', 19 | twitter: hero.twitter || '', 20 | github: hero.github || '', 21 | }; 22 | db.get('heroes') 23 | .push(newHero) 24 | .last() 25 | .write(); 26 | 27 | pubsub.publish('heroes', { addHero: newHero }); 28 | 29 | return newHero; 30 | }, 31 | deleteHero: (root, { name }, { db }) => { 32 | db.get('heroes') 33 | .remove({ name }) 34 | .write(); 35 | 36 | return true; 37 | }, 38 | }, 39 | 40 | Subscription: { 41 | heroSub: { 42 | resolve: (payload, args, context, info) => { 43 | return payload.addHero; 44 | }, 45 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator('heroes'), 46 | }, 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /apollo-server/schema.graphql: -------------------------------------------------------------------------------- 1 | ## Hero member 2 | type Hero { 3 | id: ID! 4 | name: String! 5 | image: String 6 | github: String 7 | twitter: String 8 | } 9 | 10 | input HeroInput { 11 | name: String! 12 | image: String 13 | github: String 14 | twitter: String 15 | } 16 | 17 | type Query { 18 | allHeroes: [Hero] 19 | getHero(name: String!): Hero! 20 | } 21 | 22 | type Mutation { 23 | addHero(hero: HeroInput!): Hero! 24 | deleteHero(name: String!): Boolean 25 | } 26 | 27 | type Subscription { 28 | heroSub: Hero! 29 | } 30 | -------------------------------------------------------------------------------- /apollo-server/type-defs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | export default fs.readFileSync(path.resolve(__dirname, './schema.graphql'), { encoding: 'utf8' }) 5 | -------------------------------------------------------------------------------- /apollo-server/utils/db.js: -------------------------------------------------------------------------------- 1 | import Lowdb from 'lowdb'; 2 | import FileSync from 'lowdb/adapters/FileSync'; 3 | import mkdirp from 'mkdirp'; 4 | import { resolve } from 'path'; 5 | 6 | mkdirp(resolve(__dirname, '../../live')); 7 | 8 | export const db = new Lowdb( 9 | new FileSync(resolve(__dirname, '../../live/db.json')), 10 | ); 11 | 12 | // Seed an empty DB 13 | db.defaults({}).write(); 14 | -------------------------------------------------------------------------------- /apollo.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Load .env files 4 | const { loadEnv } = require('vue-cli-plugin-apollo/utils/load-env'); 5 | const env = loadEnv([ 6 | path.resolve(__dirname, '.env'), 7 | path.resolve(__dirname, '.env.local'), 8 | ]); 9 | 10 | module.exports = { 11 | client: { 12 | service: env.VUE_APP_APOLLO_ENGINE_SERVICE, 13 | includes: ['src/**/*.{js,jsx,ts,tsx,vue,gql}'], 14 | }, 15 | service: { 16 | name: env.VUE_APP_APOLLO_ENGINE_SERVICE, 17 | localSchemaFile: path.resolve( 18 | __dirname, 19 | './node_modules/.temp/graphql/schema.json', 20 | ), 21 | }, 22 | engine: { 23 | endpoint: process.env.APOLLO_ENGINE_API_ENDPOINT, 24 | apiKey: env.VUE_APP_APOLLO_ENGINE_KEY, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /live/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "heroes": [ 3 | { 4 | "id": "5ahW7OxdZ", 5 | "name": "Evan You", 6 | "image": "https://pbs.twimg.com/profile_images/1206997998900850688/cTXTQiHm_400x400.jpg", 7 | "twitter": "youyuxi", 8 | "github": "yyx990803" 9 | }, 10 | { 11 | "id": "b0PCURlkf", 12 | "name": "Guillaume Chau", 13 | "image": "https://pbs.twimg.com/profile_images/1233016078902624256/Yh4cWtyk_400x400.jpg", 14 | "twitter": "Akryum", 15 | "github": "Akryum" 16 | }, 17 | { 18 | "id": "bneqe0dcq", 19 | "name": "Eduardo San Martin Morote", 20 | "image": "https://pbs.twimg.com/profile_images/1263046393486356486/TEuM0IAV_400x400.jpg", 21 | "twitter": "posva", 22 | "github": "posva" 23 | }, 24 | { 25 | "id": "Jq2uzIh4u", 26 | "name": "Sarah Drasner", 27 | "image": "https://pbs.twimg.com/profile_images/1225613270205091840/NyoNYuhC_400x400.jpg", 28 | "twitter": "sarah_edo", 29 | "github": "sdras" 30 | }, 31 | { 32 | "id": "KWAXP1uT5", 33 | "name": "Thorsten Lünborg", 34 | "image": "https://pbs.twimg.com/profile_images/1139622278092722177/cNVjHMVb_400x400.jpg", 35 | "twitter": "Linus_Borg", 36 | "github": "LinusBorg" 37 | }, 38 | { 39 | "id": "RX5Qbnf4z", 40 | "name": "Haoqun Jiang", 41 | "image": "https://pbs.twimg.com/profile_images/460836761199845376/dhdpOXCw_400x400.jpeg", 42 | "twitter": "haoqunjiang", 43 | "github": "sodatea" 44 | }, 45 | { 46 | "id": "CgplNv299", 47 | "name": "Kazuya Kawaguchi", 48 | "image": "https://pbs.twimg.com/profile_images/1151694505298411520/7iI5124P_400x400.png", 49 | "twitter": "kazu_pon", 50 | "github": "kazupon" 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-graphql-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "apollo": "vue-cli-service apollo:dev --generate-schema", 9 | "apollo:schema:generate": "vue-cli-service apollo:schema:generate", 10 | "apollo:schema:publish": "vue-cli-service apollo:schema:publish", 11 | "apollo:start": "vue-cli-service apollo:start" 12 | }, 13 | "dependencies": { 14 | "core-js": "^2.6.5", 15 | "graphql-type-json": "^0.2.1", 16 | "lowdb": "^1.0.0", 17 | "shortid": "^2.2.14", 18 | "vue": "^2.6.10", 19 | "vue-apollo": "^3.0.3", 20 | "vuetify": "^1.5.16" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "^3.9.0", 24 | "@vue/cli-service": "^3.9.0", 25 | "graphql-tag": "^2.9.0", 26 | "node-sass": "^4.9.0", 27 | "sass-loader": "^7.1.0", 28 | "vue-cli-plugin-apollo": "^0.21.0", 29 | "vue-template-compiler": "^2.6.10" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/icons/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/android-chrome-256x256.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00aba9 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/favicon.ico -------------------------------------------------------------------------------- /public/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/public/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 21 | 24 | 27 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/icons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/icons/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Vue + GraphQL + Apollo 12 | 17 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 45 | 46 | 47 | 48 | 54 |
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 161 | 162 | 222 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/vue-graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NataliaTepluhina/vue-graphql-presentation/60c7aa87ca8dd36877ac99a372a689da1eecf8b5/src/assets/vue-graphql.png -------------------------------------------------------------------------------- /src/components/VueHero.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 45 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import { createProvider } from './vue-apollo'; 4 | import 'vuetify/dist/vuetify.min.css'; 5 | import Vuetify from 'vuetify'; 6 | 7 | Vue.use(Vuetify); 8 | 9 | Vue.config.productionTip = false; 10 | 11 | new Vue({ 12 | apolloProvider: createProvider(), 13 | render: h => h(App), 14 | }).$mount('#app'); 15 | -------------------------------------------------------------------------------- /src/vue-apollo.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueApollo from 'vue-apollo' 3 | import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client' 4 | 5 | // Install the vue plugin 6 | Vue.use(VueApollo) 7 | 8 | // Name of the localStorage item 9 | const AUTH_TOKEN = 'apollo-token' 10 | 11 | // Http endpoint 12 | const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/graphql' 13 | 14 | // Config 15 | const defaultOptions = { 16 | // You can use `https` for secure connection (recommended in production) 17 | httpEndpoint, 18 | // You can use `wss` for secure connection (recommended in production) 19 | // Use `null` to disable subscriptions 20 | wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql', 21 | // LocalStorage token 22 | tokenName: AUTH_TOKEN, 23 | // Enable Automatic Query persisting with Apollo Engine 24 | persisting: false, 25 | // Use websockets for everything (no HTTP) 26 | // You need to pass a `wsEndpoint` for this to work 27 | websocketsOnly: false, 28 | // Is being rendered on the server? 29 | ssr: false, 30 | 31 | // Override default apollo link 32 | // note: don't override httpLink here, specify httpLink options in the 33 | // httpLinkOptions property of defaultOptions. 34 | // link: myLink 35 | 36 | // Override default cache 37 | // cache: myCache 38 | 39 | // Override the way the Authorization header is set 40 | // getAuth: (tokenName) => ... 41 | 42 | // Additional ApolloClient options 43 | // apollo: { ... } 44 | 45 | // Client local data (see apollo-link-state) 46 | // clientState: { resolvers: { ... }, defaults: { ... } } 47 | } 48 | 49 | // Call this in the Vue app file 50 | export function createProvider (options = {}) { 51 | // Create apollo client 52 | const { apolloClient, wsClient } = createApolloClient({ 53 | ...defaultOptions, 54 | ...options, 55 | }) 56 | apolloClient.wsClient = wsClient 57 | 58 | // Create vue apollo provider 59 | const apolloProvider = new VueApollo({ 60 | defaultClient: apolloClient, 61 | defaultOptions: { 62 | $query: { 63 | // fetchPolicy: 'cache-and-network', 64 | }, 65 | }, 66 | errorHandler (error) { 67 | // eslint-disable-next-line no-console 68 | console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message) 69 | }, 70 | }) 71 | 72 | return apolloProvider 73 | } 74 | 75 | // Manually call this when user log in 76 | export async function onLogin (apolloClient, token) { 77 | if (typeof localStorage !== 'undefined' && token) { 78 | localStorage.setItem(AUTH_TOKEN, token) 79 | } 80 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient) 81 | try { 82 | await apolloClient.resetStore() 83 | } catch (e) { 84 | // eslint-disable-next-line no-console 85 | console.log('%cError on cache reset (login)', 'color: orange;', e.message) 86 | } 87 | } 88 | 89 | // Manually call this when user log out 90 | export async function onLogout (apolloClient) { 91 | if (typeof localStorage !== 'undefined') { 92 | localStorage.removeItem(AUTH_TOKEN) 93 | } 94 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient) 95 | try { 96 | await apolloClient.resetStore() 97 | } catch (e) { 98 | // eslint-disable-next-line no-console 99 | console.log('%cError on cache reset (logout)', 'color: orange;', e.message) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginOptions: { 3 | apollo: { 4 | enableMocks: true, 5 | enableEngine: false 6 | } 7 | } 8 | } 9 | --------------------------------------------------------------------------------