How to use Vue with GraphQL subscriptions
GraphQL subscriptions enable real-time data updates in Vue applications through WebSocket connections for live queries and data streaming. With over 12 years of Vue.js experience since 2014 and as the creator of CoreUI, I’ve implemented real-time GraphQL features in enterprise dashboards. Apollo Client provides Vue integration with subscription support, automatically updating reactive queries when server data changes. This approach creates live applications with automatic data synchronization without manual WebSocket management.
Use Apollo Client with Vue to implement GraphQL subscriptions for real-time data updates.
Install dependencies:
npm install @apollo/client graphql graphql-ws @vue/apollo-composable
Configure Apollo Client:
// apollo.js
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client/core'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { getMainDefinition } from '@apollo/client/utilities'
import { createClient } from 'graphql-ws'
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql'
})
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4000/graphql'
})
)
const link = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
export const apolloClient = new ApolloClient({
link,
cache: new InMemoryCache()
})
Setup Apollo in Vue:
// main.js
import { createApp, provide, h } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { apolloClient } from './apollo'
import App from './App.vue'
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App)
})
app.mount('#app')
Basic subscription:
<template>
<div>
<h2>Live Messages</h2>
<div v-if='loading'>Loading...</div>
<div v-else-if='error'>Error: {{ error.message }}</div>
<div v-else>
<ul>
<li v-for='message in messages' :key='message.id'>
<strong>{{ message.user }}:</strong> {{ message.text }}
<span class='time'>{{ formatTime(message.timestamp) }}</span>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useSubscription } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageAdded {
messageAdded {
id
user
text
timestamp
}
}
`
const messages = ref([])
const { result, loading, error } = useSubscription(MESSAGE_SUBSCRIPTION)
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString()
}
// Update messages when subscription receives data
const updateMessages = computed(() => {
if (result.value?.messageAdded) {
messages.value.push(result.value.messageAdded)
}
return messages.value
})
</script>
<style scoped>
.time {
color: #666;
font-size: 12px;
margin-left: 10px;
}
</style>
Subscription with variables:
<template>
<div>
<h2>User Activity for {{ userId }}</h2>
<div v-if='loading'>Subscribing...</div>
<div v-else>
<div v-for='activity in activities' :key='activity.id' class='activity'>
{{ activity.action }} - {{ activity.timestamp }}
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useSubscription } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const props = defineProps({
userId: {
type: String,
required: true
}
})
const USER_ACTIVITY_SUBSCRIPTION = gql`
subscription OnUserActivity($userId: ID!) {
userActivity(userId: $userId) {
id
action
timestamp
}
}
`
const activities = ref([])
const { result, loading } = useSubscription(
USER_ACTIVITY_SUBSCRIPTION,
() => ({
userId: props.userId
})
)
// Watch for new activity
watch(result, (newResult) => {
if (newResult?.userActivity) {
activities.value.unshift(newResult.userActivity)
}
})
</script>
Combining query and subscription:
<template>
<div>
<h2>Posts</h2>
<div v-if='loading'>Loading posts...</div>
<div v-else>
<article v-for='post in posts' :key='post.id' class='post'>
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
<span class='likes'>{{ post.likes }} likes</span>
</article>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useQuery, useSubscription } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const POSTS_QUERY = gql`
query GetPosts {
posts {
id
title
content
likes
}
}
`
const POST_UPDATED_SUBSCRIPTION = gql`
subscription OnPostUpdated {
postUpdated {
id
likes
}
}
`
const posts = ref([])
// Initial query
const { result: queryResult, loading } = useQuery(POSTS_QUERY)
watch(queryResult, (newResult) => {
if (newResult?.posts) {
posts.value = newResult.posts
}
})
// Real-time updates
const { result: subResult } = useSubscription(POST_UPDATED_SUBSCRIPTION)
watch(subResult, (newResult) => {
if (newResult?.postUpdated) {
const index = posts.value.findIndex(p => p.id === newResult.postUpdated.id)
if (index !== -1) {
posts.value[index].likes = newResult.postUpdated.likes
}
}
})
</script>
Live dashboard with subscriptions:
<template>
<div class='dashboard'>
<div class='metrics'>
<div class='metric-card' v-for='metric in metrics' :key='metric.name'>
<h3>{{ metric.name }}</h3>
<div class='value'>{{ metric.value }}</div>
<div class='change' :class='metric.change > 0 ? "positive" : "negative"'>
{{ metric.change > 0 ? '+' : '' }}{{ metric.change }}%
</div>
</div>
</div>
<div class='chart'>
<h3>Real-time Analytics</h3>
<div v-for='point in dataPoints' :key='point.timestamp' class='data-point'>
{{ point.value }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useSubscription } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const METRICS_SUBSCRIPTION = gql`
subscription OnMetricsUpdate {
metricsUpdated {
name
value
change
}
}
`
const ANALYTICS_SUBSCRIPTION = gql`
subscription OnAnalyticsData {
analyticsData {
timestamp
value
}
}
`
const metrics = ref([
{ name: 'Revenue', value: 0, change: 0 },
{ name: 'Users', value: 0, change: 0 },
{ name: 'Orders', value: 0, change: 0 }
])
const dataPoints = ref([])
const { result: metricsResult } = useSubscription(METRICS_SUBSCRIPTION)
const { result: analyticsResult } = useSubscription(ANALYTICS_SUBSCRIPTION)
watch(metricsResult, (newResult) => {
if (newResult?.metricsUpdated) {
const index = metrics.value.findIndex(m => m.name === newResult.metricsUpdated.name)
if (index !== -1) {
metrics.value[index] = newResult.metricsUpdated
}
}
})
watch(analyticsResult, (newResult) => {
if (newResult?.analyticsData) {
dataPoints.value.push(newResult.analyticsData)
if (dataPoints.value.length > 50) {
dataPoints.value.shift()
}
}
})
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.value {
font-size: 32px;
font-weight: bold;
margin: 10px 0;
}
.change {
font-size: 14px;
font-weight: bold;
}
.positive {
color: #28a745;
}
.negative {
color: #dc3545;
}
.chart {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
Chat with subscriptions:
<template>
<div class='chat'>
<div class='messages'>
<div v-for='msg in messages' :key='msg.id' class='message'>
<strong>{{ msg.user }}:</strong> {{ msg.text }}
</div>
</div>
<div class='input'>
<input v-model='messageText' @keyup.enter='sendMessage' placeholder='Type message...'>
<button @click='sendMessage'>Send</button>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useSubscription, useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageAdded {
messageAdded {
id
user
text
}
}
`
const SEND_MESSAGE_MUTATION = gql`
mutation SendMessage($user: String!, $text: String!) {
sendMessage(user: $user, text: $text) {
id
user
text
}
}
`
const messages = ref([])
const messageText = ref('')
const currentUser = ref('User' + Math.floor(Math.random() * 1000))
const { result } = useSubscription(MESSAGE_SUBSCRIPTION)
const { mutate: sendMessageMutation } = useMutation(SEND_MESSAGE_MUTATION)
watch(result, (newResult) => {
if (newResult?.messageAdded) {
messages.value.push(newResult.messageAdded)
}
})
const sendMessage = async () => {
if (messageText.value.trim()) {
await sendMessageMutation({
user: currentUser.value,
text: messageText.value
})
messageText.value = ''
}
}
</script>
Best Practice Note
Apollo Client automatically manages WebSocket connections for subscriptions. Use split to route subscriptions through WebSocket and queries through HTTP. Subscriptions work seamlessly with Vue’s reactivity—updates trigger re-renders. Combine queries for initial data with subscriptions for real-time updates. Handle connection errors and reconnection in Apollo configuration. Use variables to filter subscription data on the server. This is how we implement GraphQL subscriptions in CoreUI for Vue—providing real-time dashboards, live data feeds, and collaborative features with automatic data synchronization for modern web applications.



