Skip to content

Getting Started

rstore is a data store allowing you to handle all data in your application.

Define a data model and then run queries or execute mutations (create, update and delete) on your data.

FEATURES

  • Normalized reactive cache to ensure all components are up-to-date
  • Co-locate queries within the components that need them
  • Fully adaptable with plugins to fetch from any source (REST, GraphQL...)
  • Scale down to small prototypes and scale up to big enterprise apps
  • Query API designed for local-first and realtime
  • Form API to handle form state and validation
  • TypeScript support with full autocomplete
  • Nuxt module with devtools

Learn more

directus logodirectus logoBacked by Directus

Vue

  1. Install rstore:
sh
npm i @rstore/vue
sh
pnpm i @rstore/vue
  1. Create some Models:
js
import { defineDataModel } from '@rstore/vue'

export const todoModel = defineDataModel({
  name: 'todos',
})
ts
import type { ModelList } from '@rstore/vue'
import { defineItemType } from '@rstore/vue'

// Item type
export interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: Date
  updatedAt?: Date
}

// Model
const todoModel = defineItemType<Todo>().model({
  name: 'todos',
})

export const models = [
  todoModel,
] satisfies ModelList
  1. Create a plugin to interact with an API:
js
import { definePlugin } from '@rstore/vue'

export default definePlugin({
  name: 'my-rstore-plugin',

  setup({ hook }) {
    // Register rstore hooks here
  },
})

IMPORTANT

By default, rstore doesn't make any assumption about the way you fetch data in your app. Plugins can hook into it to provide fetching logic (for example to make requests to a REST API).

Example for a simple REST API:

js
export default definePlugin({
  name: 'my-rstore-plugin',

  setup({ hook }) {
    hook('fetchFirst', async (payload) => {
      if (payload.key) {
        const result = await fetch(`/api/${payload.model.name}/${payload.key}`)
          .then(r => r.json())
        payload.setResult(result)
      }
    })

    hook('fetchMany', async (payload) => {
      const result = await fetch(`/api/${payload.model.name}`)
        .then(r => r.json())
      payload.setResult(result)
    })

    hook('createItem', async (payload) => {
      const result = await fetch(`/api/${payload.model.name}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload.item),
      }).then(r => r.json())
      payload.setResult(result)
    })

    hook('updateItem', async (payload) => {
      const result = await fetch(`/api/${payload.model.name}/${payload.key}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload.item),
      }).then(r => r.json())
      payload.setResult(result)
    })

    hook('deleteItem', async (payload) => {
      await fetch(`/api/${payload.model.name}/${payload.key}`, {
        method: 'DELETE',
      })
    })
  },
})

INFO

In the future rstore will provide some builtin plugins for GraphQL, OpenAPI and other popular standards. Feel free to also share your own plugins with the community! 😸

  1. Create the store:
js
import { createStore } from '@rstore/vue'
import { todoModel } from './model'
import myPlugin from './plugin'

export async function rstore(app) {
  const store = await createStore({
    models: [
      todoModel,
    ],
    plugins: [
      myPlugin,
    ],
  })
}
ts
import type { App } from 'vue'
import { createStore } from '@rstore/vue'
import { models } from './model'
import myPlugin from './plugin'

export async function rstore(app: App) {
  const store = await createStore({
    models,
    plugins: [
      myPlugin,
    ],
  })
}
  1. Expose the store to your components with a composable:
js
const injectStoreKey = Symbol('rstore')

export async function rstore(app) {
  const store = await createStore({
    // ...
  })

  app.provide(injectStoreKey, store)
}

export function useStore () {
  const store = inject(injectStoreKey, null)
  if (store == null) {
    throw new Error('No rstore provided.')
  }
  return store
}
ts
import type { VueStore } from '@rstore/vue'
import type { InjectionKey } from 'vue'

const injectStoreKey = Symbol('rstore') as InjectionKey<VueStore<typeof models>>

export async function rstore(app: App) {
  const store = await createStore({
    // ...
  })

  app.provide(injectStoreKey, store)
}

export function useStore () {
  const store = inject(injectStoreKey, null)
  if (store == null) {
    throw new Error('No rstore provided.')
  }
  return store
}
  1. Add the store to your app:
js
import { rstore } from './rstore'

app.use(rstore)
  1. Use the store in a component:
vue
<script setup>
import { useStore } from '@/rstore'

const store = useStore()

const { data: todos } = store.todos.queryMany()
</script>

<template>
  <pre>{{ todos }}</pre>
</template>

Nuxt

The Nuxt module will automatically:

  • scan the rstore folder in your Nuxt app for models,
  • scan the rstore/plugins folder and register plugins (using export default),
  • create the store
  • handle SSR payload
  • expose the useStore composable typed according to the model (from the rstore folder).

  1. Install rstore and add it to the Nuxt config:
sh
npm i @rstore/nuxt
sh
pnpm i @rstore/nuxt
ts
export default defineNuxtConfig({
  modules: [
    '@rstore/nuxt',
  ],
})
  1. Create some Models in the rstore (or app/rstore) folder of your Nuxt app:
ts
// One Model
export default defineItemType<Todo>().model({
  name: 'todos',
})
ts
// Multiple Models
export default [
  defineItemType<User>().model({
    name: 'users',
  }),

  defineItemType<Bot>().model({
    name: 'bots',
  }),
]
ts
// Item types

export interface Todo {
  id: string
  text: string
  completed: boolean
  createdAt: Date
  updatedAt?: Date
}

export interface User {
  id: string
  name: string
  email: string
}

export interface Bot {
  id: string
  name: string
}

FILE SCANNING

The rstore module will only scan exported defaults in files in the rstore folder and not in nested folders. If you want to split the models in multiple folders, you need to re-export each variables (currently the module is looking for export default expressions) or use Nuxt layers (recommended).

  1. Create a plugin to interact with an API in the rstore/plugins folder:
ts
export default defineRstorePlugin({
  name: 'my-rstore-plugin',

  setup({ hook }) {
    // Register rstore hooks here
  },
})

IMPORTANT

By default, rstore doesn't make any assumption about the way you fetch data in your app. Plugins can hook into it to provide fetching logic (for example to make requests to a REST API).

Example for a simple REST API:

js
export default defineRstorePlugin({
  name: 'my-rstore-plugin',

  setup({ hook }) {
    hook('fetchFirst', async (payload) => {
      if (payload.key) {
        const result = await $fetch(`/api/${payload.model.name}/${payload.key}`)
        payload.setResult(result)
      }
    })

    hook('fetchMany', async (payload) => {
      const result = await $fetch(`/api/${payload.model.name}`)
      payload.setResult(result)
    })

    hook('createItem', async (payload) => {
      const result = await $fetch(`/api/${payload.model.name}`, {
        method: 'POST',
        body: payload.item,
      })
      payload.setResult(result)
    })

    hook('updateItem', async (payload) => {
      const result = await $fetch(`/api/${payload.model.name}/${payload.key}`, {
        method: 'PATCH',
        body: payload.item,
      })
      payload.setResult(result)
    })

    hook('deleteItem', async (payload) => {
      await $fetch(`/api/${payload.model.name}/${payload.key}`, {
        method: 'DELETE',
      })
    })
  },
})

INFO

In the future rstore will provide some builtin plugins for GraphQL, OpenAPI and other popular standards. Feel free to also share your own plugins with the community! 😸

  1. Use the store in a component:
vue
<script setup>
const store = useStore()

const { data: todos } = await store.todos.queryMany()
</script>

<template>
  <pre>{{ todos }}</pre>
</template>

Open the Nuxt devtools and check the rstore tab:

Devtools screenshot of the models tab

Devtools screenshot of the history tab

Nuxt + Drizzle

Online Demo

In case you are using Drizzle, you can install the @rstore/nuxt-drizzle module instead of @rstore/nuxt to automatically generate the models and plugins from your drizzle schema.

  1. Install @rstore/nuxt-drizzle and add it to the Nuxt config:
sh
npm i @rstore/nuxt-drizzle
ts
export default defineNuxtConfig({
  modules: [
    '@rstore/nuxt-drizzle',
  ],
})
  1. Expose a function to return a drizzle instance:
ts
// server/utils/drizzle.ts

import { drizzle } from 'drizzle-orm/libsql'

let drizzleInstance: ReturnType<typeof drizzle> | null = null

export function useDrizzle() {
  drizzleInstance ??= drizzle({
    connection: { url: useRuntimeConfig().dbUrl },
    casing: 'snake_case',
  })
  return drizzleInstance
}

The module will automatically:

  • load the drizzle schema from the drizzle.config.ts file (configurable with the rstoreDrizzle.drizzleConfigPath option in the Nuxt config),
  • generate the models from the schema for each table with the relations,
  • generate a REST API under the /api/rstore path to handle the CRUD operations,
  • generate a plugin to handle the queries and mutations,
  • generate all the necessary types for the models and the API.

You can already use the store in your components without any additional configuration:

vue
<script setup>
const store = useStore()

const { data: todos } = await store.todos.queryMany()
</script>

<template>
  <pre>{{ todos }}</pre>
</template>

Continue to the plugin documentation ➜

Nuxt + Directus

You can use the @rstore/nuxt-directus module to automatically generate the models and plugins from your Directus backend.

bash
npm install @rstore/nuxt-directus
ts
export default defineNuxtConfig({
  modules: [
    '@rstore/nuxt-directus',
  ],

  rstoreDirectus: {
    url: 'https://your-directus-instance.com', // The URL of your Directus instance
    adminToken: import.meta.env.DIRECTUS_TOKEN, // The admin token you created in step 2
  },
})

Continue to the plugin documentation ➜