πŸ’šFramework Integration

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-here

For 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 pinia

Create 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