Skip to content
On this page
v2.0.0-pre.18

What's New in 2.0

Feathers-Pinia 2.0 is a huge update with some great new features. This page will go over some of the highlights.

Huge Performance Boost 🚀

Feathers-Pinia is SO MUCH faster than its predecessor. You'll see massive benefits from the faster reactive types under the hood of Pinia and Vue 3. But we've gone a step further and fine-tuned and tested Feathers-Pinia to never perform extra work. Some of the biggest improvements are:

  • No unnecessary stack frames happen under the hood. We stand firmly against wasted CPU cycles!
  • As from the beginning, you still have full control over adding instances to the store with instance.addToStore().
  • For the features that require objects to be in the store (for example, useClones) feathers-pinia will implicitly add items to the store when needed.

Composition API Stores 🎉

Feathers-Pinia has been completely rewritten as a set of Composition API utilities for creating Pinia setup stores. The advantages include a better TypeScript experience, cleaner customization, and fewer limitations.

useService 🎁

The new useService utility takes the place of the Feathers-Pinia defineStore utility (not to be confused Pinia's defineStore utility) and gives you a starting point for defining your own setup store for each service. The object returned from calling useService has the same shape as service stores from older versions.

The useService utility only requires a Feathers service and not the full Feathers client instance, anymore. It also requires the use of a Model Function, which is covered further down this page and not shown in this example. This example shows the creation and instantiation of a setup store with useService:

ts
import { defineStore, createPinia } from 'pinia'

const pinia = createPinia()

// Create a tasks store
export const useTaskStore = defineStore('tasks', () => {
  const serviceUtils = useService<TaskInstance, TasksData, TasksQuery, typeof modelFn>({
    service,
    idField: '_id',
    Model: Task, // see the section on Model Functions
  })

  return { ...serviceUtils }
})

const taskStore = useTaskStore(pinia)

To customize a setup store, you declare additional variables, computed properties, and functions inside of the call to defineStore.

Learn more about the new useService utility.

useAuth 🎁

Create ultra-flexible setup stores with the new useAuth utility:

ts
// src/store/store.auth.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useAuth } from 'feathers-pinia'

export const useAuthStore = defineStore('auth', () => {
  const { userStore } = useUserStore()
  const { api } = useFeathers()

  const auth = useAuth({
    api,
    userStore,
  })

  auth.reAuthenticate()

  return auth
})

Learn more about the new useAuth utility

Feathers v5 Dove TS Support 🎉

The new utilities in Feathers-Pinia 2.0 bring support for the new TypeScript enhancements in Feathers v5 Dove. Now you can directly import the types from your backend and use them in your Feathers-Pinia frontend. The types integrate directly into the new Model Functions, as well.

Learn more about Feathers v5 Dove types in the Feathers documentation:

Model Functions, not Classes 🔮

Data modeling is one of the most-loved features in Feathers-Pinia. In Feathers-Pinia 2.0, we replace Model Classes with Model Functions. The developer experience is so much better! You just create a function that receives an object, performs modifications to it, then returns it. There are two utilities for wrapping Model Functions: useFeathersModel and useBaseModel.

Learn more about Model Functions

useFeathersModel 🎁

The useFeathersModel utility is most similar to the old BaseModel class from FeathersVuex and previous versions of Feathers-Pinia. You get the full Feathers service experience. Feathers Models have Feathers-related methods, like find, count, get, etc., directly on the model interface.

Note about Feathers Types

Replace my-feathers-api in the below example with the package installed from your Feathers v5 Dove API. You can also provide manual types that describe the shape of your data.

ts
import type { Tasks, TasksData, TasksQuery } from 'my-feathers-api'
import { type ModelInstance, useFeathersModel, useInstanceDefaults } from 'feathers-pinia'
import { api } from '../feathers'

const modelFn = (data: ModelInstance<Tasks>) => {
  const withDefaults = useInstanceDefaults({ description: '', isComplete: false }, data)
  return withDefaults
}
const Task = useFeathersModel<Tasks, TasksData, TasksQuery, typeof modelFn>(
  { name: 'Task', idField: '_id', service },
  modelFn,
)

Models now come with a lightweight, built-in store. This means that they work without a Pinia store, but you give up the Pinia devtools compatibility. To upgrade to a full Pinia store, use the setStore method to provide the instantiated pinia store:

ts
// upgrading the Task Model's store to be a Pinia store
Task.setStore(taskStore)

To create a model "instance" you just call the function WITHOUT the new operator:

ts
const task = Task({ description: 'Do the dishes' })

Learn more about the new useFeathersModel utility

useBaseModel 🎁

The useBaseModel utility gives you all of the BaseModel functionality without the Feathers parts. This means you can work with non-service data using the same APIs. Base model functions also come with a built-in store, and you can even perform queries on the data with store getters. Also, useBaseModel instances come with clone, commit and reset methods.

Note about Feathers Types

Replace my-feathers-api in the below example with the package installed from your Feathers v5 Dove API. You can also provide manual types that describe the shape of your data.

ts
import type { Tasks, TasksData, TasksQuery } from 'my-feathers-api'
import { type ModelInstance, useBaseModel, useInstanceDefaults } from 'feathers-pinia'

const modelFn = (data: ModelInstance<Tasks>) => {
  const withDefaults = useInstanceDefaults({ description: '', isComplete: false }, data)
  return withDefaults
}
const Task = useBaseModel<Tasks, TasksQuery, typeof modelFn>({ name: 'Task', idField: '_id' }, modelFn)

BaseModel functions also have a store, which can be upgraded to a full Pinia store using the setStore method:

ts
// upgrading the Task Model's store to be a Pinia store
Task.setStore(taskStore)

To create a model "instance" you just call the function WITHOUT the new operator:

ts
const task = Task({ description: 'Do the dishes' })

Learn more about the new useBaseModel utility

useInstanceDefaults 🎁

You can define default values for instances using the useInstanceDefaults. This takes the place of the former BaseModel class's instanceDefaults method.

Learn more about the new useInstanceDefaults utility

useFeathersInstance 🎁

There's also a useFeathersInstance utility which you can use with useBaseModel. It's used inside of your Model function to update a model instance to support Feathers-related methods, like instance.save().

Learn more about the new useFeathersInstance utility

New Model Methods

FeathersModels now include methods for useFind, useGet, and useGetOnce.

Learn more on the useFeathersModel page.

Official Nuxt Module 🎁

Feathers-Pinia v2 comes with a module for Nuxt which registers auto-imports and provides Nuxt-specific utilities for data modeling.

Learn more about the new Nuxt Module

Auto-Imports Support ⚡️

Since Feathers-Pinia v2 is so modular, import statements can be verbose. New Auto-Import support for Nuxt, Vite, Webpack, Rollup, and more, is provided through the new unplugin-auto-imports preset.

Learn more about the new Auto-Imports Support.

Feathers Client Hooks 🪝

Feathers-Pinia now fully integrates with the Feathers Client through a new set of feathersPiniaHooks. The majority of the store logic was moved into the hooks, so you get the same experience whether you use the Feathers client, the Model Functions, or the store methods. The hooks are required in order for Feathers-connected Models or stores to work.

The above example builds on the Model function that was created in the previous useFeathersModel example. For an all-in-one example, see one of the setup pages.

ts
import { feathersPiniaHooks } from 'feathers-pinia'
import { api } from '../feathers'

/* setup the Model function as shown earlier */

// Pass the model function to the utility in the `around all` hooks.
api.service('tasks').hooks({ around: { all: [...feathersPiniaHooks(Task)] } })

Learn more about the new hooks for Feathers Client.

Query API Reference 📖

The documentation now includes a page about supported query props. It's a great reference for what query props are supported by:

Learn more on the new Querying Data page.

SQL $like Operators 🎁

The most-requested feature has finally landed: built-in support for SQL LIKE. This means the queries made to the store will match the queries made to your SQL-backed API. This brings querying features up to parity with the built-in MongoDB support which uses the $regex key.

These are the newly-supported SQL operators:

  • $like and $notLike for case-sensitive matches
  • $ilike, $iLike, and $notILike for case-insensitive matches

Let's have a look at them in action. First, assume that we have the following messages in the store:

json
[
  { "id": 1, "text": "Moose" },
  { "id": 2, "text": "moose" },
  { "id": 3, "text": "Goose" },
  { "id": 4, "text": "Loose" },
]

Now see the fancy new query operators in action:

ts
import { useMessages } from '../stores/messages'
const messageStore = useMessages

// $like
const { data: data1 } = messageStore.findInStore({
  query: { text: { $like: '%Mo%' } }
})
expect(data1.map((m) => m.id)).toEqual([1])

// $notLike
const { data: data2 } = messageStore.findInStore({
  query: { text: { $notLike: '%Mo%' } }
})
expect(data2.map((m) => m.id)).toEqual([2, 3, 4])

// $ilike
const { data: data3 } = messageStore.findInStore({
  query: { text: { $ilike: '%Mo%' } }
})
expect(data3.map((m) => m.id)).toEqual([1, 2])

// $iLike
const { data: data4 } = messageStore.findInStore({
  query: { text: { $iLike: '%Mo%' } }
})
expect(data4.map((m) => m.id)).toEqual([1, 2])

// $notILike
const { data: data5 } = messageStore.findInStore({
  query: { text: { $notILike: '%Mo%' } }
})
expect(data5.map((m) => m.id)).toEqual([3, 4])

These new operators support queries made with SQL-backed adapters like the official, core SQL service adapter in Feathers v5 Dove:

These adapters will also work:

If you use any of the above database adapters, give the new query operators a try! Enjoy your new superpowers!

Read more about all supported query filters and operators on the Querying Data page.

params.clones

You can now pass params.clones to either findInStore or getFromStore to return all matching data as clones of the original data. This was formerly known as params.copies in Feathers-Vuex.

Learn more in the Querying Data page

Built-in Patch Diffing 🎁

Efficiency Tip

Don't waste bandwidth! Just send the props that change!

Patch diffing, which originated in Feathers-Vuex, is now back in Feathers-Pinia with a smarter, faster algorithm that will work for any scenario you can dream up.

Diffing only occurs on patch requests (and when calling instance.save() calls a patch).

ts
// clone a record
const clone = user.clone()
// make changes
clone.name = 'Feathers is Amazing!'
// save
await clone.save(). // --> Only the changed props go to the server!

How It Works

  • By default, all keys are deep-compared between the original record and the clone.
  • Once all changes are found, only the top-level keys are sent to the server.

Diffing will work on all databases without data loss.

Customize the Diff

You can use the diff option to customize which values are compared. Only props that have changed will be sent to the server.

ts
// string: diff only this prop
await clone.save({ diff: 'teamId' )

// array: diff only these props
await clone.save({ diff: ['teamId', 'username'] )

// object: merge and diff these props
await clone.save({ diff: { teamId: 1, username: 'foo' } )

// or turn off diffing and send everything
await clone.save({ diff: false })

Always Save Certain Props

If there are certain props that need to always go with the request, use the with option:

ts
// string: always include this prop
await clone.save({ with: 'teamId' )

// array: always include these props
await clone.save({ with: ['teamId', 'username'] )

// object: merge and include these props
await clone.save({ with: { teamId: 1, username: 'foo' } )

Specify Patch Data

When calling .save() or .patch(), you can provide an object as params.data, and Feathers-Pinia will use it as the patch data. This bypasses the diff and with params.

js
const task = Task({ description: 'Do Something', isComplete: false })
await task.patch({ data: { isComplete: true } })

Read more about FeathersModel Instances

Reactive Model Instances ➕

Thanks to the Model Function API, all Model instances are now always reactive, even when not added to the store.

ts
import { Task } from '../models/task'

const task = Task({ description: 'Bind me to a template. I am ready.' })

Read more about Model Instances.

Handle Associations

Two new utilities make it easier to add relationships between records without depending on associations in-memory. You can setup associations in both directions between models.

Read more about Association Patterns.

associateFind 🎁

The associateFind utility allows you to define one-to-many relationships on your Model functions. The makeParams property allows you to specify the query that defines the relationship. This example is truncated. For a full example, see the "Start a Project" pages.

ts
import type { Users } from 'my-feathers-api'
import { type ModelInstance, useInstanceDefaults, associateFind } from 'feathers-pinia'
import { Message } from './message'

const modelFn = (data: ModelInstance<Users>) => {
  const withDefaults = useInstanceDefaults({ email: '', password: '' }, data)
  const withMessages = associateFind(withDefaults, 'messages', {
    Model: Message,
    makeParams: (data) => ({ query: { userId: data.id } }),
    handleSetInstance(message) {
      message.userId = data.id
    },
  })
  return withMessages
}

The handleSetInstance function allows you to write data to the messages property and make sure each record becomes properly associated.

Read more about associateFind

associateGet 🎁

The associateGet utility allows you to define one-to-one relationships on your Model functions. The getId property allows you to specify the id to use to get the related data.

ts
import type { Messages } from 'my-feathers-api'
import { type ModelInstance, useInstanceDefaults, associateGet } from 'feathers-pinia'
import { User } from './user'

const modelFn = (data: ModelInstance<Messages>) => {
  const withDefaults = useInstanceDefaults({ text: '', userId: null }, data)
  const withUser = associateGet(withDefaults, 'user', {
    Model: User,
    getId: (data) => data.messageId
  })
  return withUser
}

Read more about associateGet

New useFind API 🎁

The useFind API has been completely rewritten from scratch. A couple of its best features include

  • Intelligent Fall-Through Caching - Like SWR, but way smarter.
  • Pagination Support - Built in, sharing the same logic with usePagination.

See all of the features on the useFind page.

See the component example of server-side pagination on the useFind page.

useFindWatched API ⚠️

To make migration to the new useFind API easier, the old useFind API has been renamed and is now called useFindWatched.

Learn more about the old API on the useFindWatched page.

See the new API on the useFind page.

Store API Improvements

The useFind utility -- for implementing fall-through-cached find requests -- is now available directly on the store, further reducing boilerplate.

store.useFind

With the old way, you have to import useFind and provide the model to it from the instantiated store.

ts
import { useFind } from 'feathers-pinia'
import { useTutorials } from '../store/tutorials'

const tutorialStore = useTutorials()
const tutorialsParams = computed(() => {
  return { query: {}, }
})
const { items: tutorials } = useFind({ model: tutorialStore.Model, params: tutorialsParams })

In the new way, there's no need to import useFind. Call it as a method on the store and don't pass model

ts
import { useTutorials } from '../store/tutorials'

const tutorialStore = useTutorials()
const tutorialsParams = computed(() => {
  return { query: {}, }
})
const { items: tutorials } = tutorialStore.useFind({ params: tutorialsParams })

Think of all of the extra time you'll have instead of having to write those 1.5 lines of code over and over again! 😁

store.useGet

The useGet utility -- for implementing fall-through-cached get requests -- is now available directly on the store, further reducing boilerplate.

Removals ➖

The following modules are no longer included:

LZW Storage is Out

Prior to this version, Feathers-Pinia included a localStorage plugin that used LZW compression. It came with the benefit of doubling the amount of data you could put in storage. The downside was that it made the bundle size big, so we removed it. It will be published as an independent package at a later date.

Our LocalStorage adapter remains part of the package and is so fast that it makes Single Page Apps feel like they're doing Server Side Rendering. If you haven't tried it, yet, it's easy to setup and it's worth it!

No More defineAuthStore

The useAuth utility takes the place of defineAuthStore.

See how to migrate from defineAuthStore to useAuth

No instance.update() method

The rarely-used update method has been removed from the instance interface. Use the patch method, instead, to take advantage of patch diffing and partial updates. You can still replace an entire object by just sending all of the data through patch. The Model Functions and Feathers-connected stores continue to have an update method, which an also be used.

Read more about Model Instances.

Many thanks go to the Vue, Vuex, Pinia, and FeathersJS communities for keeping software development FUN!