Integrating MeshBase with Vue.js
Build progressive Vue.js applications powered by MeshBase. Composition API, composables, Piniaβall the modern Vue patterns.
Why MeshBase + Vue.js?
π―Composables-First
Vue composables for data fetching. Clean, reusable patterns that feel natural in Vue 3.
β‘Reactive by Default
Vue's reactivity system works perfectly with MeshBase. Data updates, UI updates automatically.
πNuxt.js Ready
Server-side rendering, static generation, and hybrid modes all work seamlessly with MeshBase.
πState Management
Integrate with Pinia or Vuex for global state management with MeshBase data.
What You'll Need
- βVue 3+ project (Composition API recommended)
- βA MeshBase account with content types defined
- βYour MeshBase public API key
Quick Start: Composition API
Modern Vue 3 with Composition APIβthe recommended approach.
1. Add your API key
Create .env:
VITE_MESHBASE_API_URL=https://api.meshbase.io/v1
VITE_MESHBASE_API_KEY=your-api-key-hereFor Vue CLI, use VUE_APP_ prefix instead of VITE_
2. Create a composable
Create composables/useMeshBase.js:
import { ref, onMounted } from 'vue';
const API_URL = import.meta.env.VITE_MESHBASE_API_URL;
const API_KEY = import.meta.env.VITE_MESHBASE_API_KEY;
export function useMeshBase(endpoint) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
async function fetchData() {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
const json = await response.json();
data.value = json.data;
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
onMounted(fetchData);
return { data, loading, error, refetch: fetchData };
}3. Use it in your components
<script setup>
import { useMeshBase } from '@/composables/useMeshBase';
const { data: posts, loading, error } = useMeshBase('/blog-posts');
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else class="blog-list">
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
<router-link :to="`/blog/${post.id}`">
Read more
</router-link>
</article>
</div>
</div>
</template>That's It!
Your Vue app now fetches content from MeshBase using the Composition API. The composable handles loading states, errors, and reactive data automatically.
TypeScript Version
Using TypeScript? Here's a type-safe version.
Create composables/useMeshBase.ts:
import { ref, onMounted, type Ref } from 'vue';
const API_URL = import.meta.env.VITE_MESHBASE_API_URL;
const API_KEY = import.meta.env.VITE_MESHBASE_API_KEY;
interface UseMeshBaseReturn<T> {
data: Ref<T[] | null>;
loading: Ref<boolean>;
error: Ref<string | null>;
refetch: () => Promise<void>;
}
export function useMeshBase<T>(endpoint: string): UseMeshBaseReturn<T> {
const data = ref<T[] | null>(null);
const loading = ref(true);
const error = ref<string | null>(null);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch(`${API_URL}${endpoint}`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
const json = await response.json();
data.value = json.data;
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading.value = false;
}
}
onMounted(fetchData);
return { data, loading, error, refetch: fetchData };
}
// Typed helper
export interface BlogPost {
id: string;
title: string;
excerpt: string;
content: string;
coverImage?: string;
}
export function useBlogPosts() {
return useMeshBase<BlogPost>('/blog-posts');
}Usage with types:
<script setup lang="ts">
import { useBlogPosts } from '@/composables/useMeshBase';
const { data: posts, loading, error } = useBlogPosts();
// TypeScript knows posts is BlogPost[] | null
// Auto-completion works!
</script>Pinia State Management
For global state, integrate MeshBase with Pinia (Vue's official state management).
Install Pinia
npm install piniaCreate a content store
Create stores/content.js:
import { defineStore } from 'pinia';
const API_URL = import.meta.env.VITE_MESHBASE_API_URL;
const API_KEY = import.meta.env.VITE_MESHBASE_API_KEY;
export const useContentStore = defineStore('content', {
state: () => ({
posts: [],
loading: false,
error: null,
}),
actions: {
async fetchPosts() {
this.loading = true;
this.error = null;
try {
const response = await fetch(`${API_URL}/blog-posts`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
const json = await response.json();
this.posts = json.data;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
},
getters: {
publishedPosts: (state) =>
state.posts.filter(p => p.publishedAt),
},
});Use in components
<script setup>
import { useContentStore } from '@/stores/content';
import { onMounted } from 'vue';
const store = useContentStore();
onMounted(() => {
store.fetchPosts();
});
</script>
<template>
<div>
<div v-if="store.loading">Loading...</div>
<div v-else>
<article v-for="post in store.publishedPosts" :key="post.id">
<h2>{{ post.title }}</h2>
</article>
</div>
</div>
</template>When to Use Pinia
Use Pinia when you need to share MeshBase data across multiple components. For component-local data, composables are simpler.
Nuxt.js Integration
Using Nuxt? Server-side rendering and static generation work seamlessly.
Server-side data fetching
Use useFetch or useAsyncData:
<script setup>
const config = useRuntimeConfig();
const { data: posts, pending, error } = await useFetch('/blog-posts', {
baseURL: config.public.meshbaseApiUrl,
headers: {
Authorization: `Bearer ${config.public.meshbaseApiKey}`
},
transform: (res) => res.data
});
</script>
<template>
<div>
<div v-if="pending">Loading...</div>
<div v-else>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
</article>
</div>
</div>
</template>Nuxt config
Add to nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
public: {
meshbaseApiUrl: process.env.MESHBASE_API_URL,
meshbaseApiKey: process.env.MESHBASE_API_KEY
}
}
})Static generation
Generate static pages for all blog posts:
// pages/blog/[id].vue
<script setup>
const route = useRoute();
const config = useRuntimeConfig();
const { data: post } = await useFetch(`/blog-posts/${route.params.id}`, {
baseURL: config.public.meshbaseApiUrl,
headers: {
Authorization: `Bearer ${config.public.meshbaseApiKey}`
}
});
</script>
<template>
<article>
<h1>{{ post.title }}</h1>
<div v-html="post.content"></div>
</article>
</template>Options API (Vue 2 / Legacy)
Still using Options API? Here's the pattern.
<script>
const API_URL = process.env.VUE_APP_MESHBASE_API_URL;
const API_KEY = process.env.VUE_APP_MESHBASE_API_KEY;
export default {
data() {
return {
posts: [],
loading: true,
error: null,
};
},
async mounted() {
try {
const response = await fetch(`${API_URL}/blog-posts`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Failed to fetch');
const json = await response.json();
this.posts = json.data;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
};
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
</article>
</div>
</div>
</template>Next Steps
- βHandle images and media
Upload and serve media files
- βExplore the full API
Filtering, sorting, pagination, and more
- βCheck out Svelte integration
Using Svelte or SvelteKit