Skip to content
On this page

What's New in 3.0

Feathers-Pinia 3.0 finally gives us the magical, implicit API that we enjoyed with Feathers-Vuex, but in a smaller, implicitly modular and much faster package. This page will go over more of the highlights.

Leading the Simple Life

Complex setup is a thing of the past. Version 3 combines the best patterns from all previous versions, makes everything implicit, and almost does your job for you. The result is a work of art.

You can configure all services in one line of code:

ts
// src/feathers.ts
import { pinia } from './plugins/pinia'

const feathersClient = {} // See the Feathers Client install/setup pages
const api = createVueClient(feathersClient, { pinia, idField: '_id' })

That line of code gives you a lot:

  • A wrapped Feathers Client
  • All Feathers Service Interface methods and some extras like findOne, for convenience.
  • All of the local, data-related store methods, with its Live Queries and Lists.
  • Pinia stores created on the fly.
  • Implicit Models created for you. Manually wrangling stores and Models is no longer even an option.

Data modeling still runs the show "under the hood," and all data is turned into Feathers instances, by default. You can customize instances using the services option of the config with a setupInstance method, like this:

ts
// src/feathers.ts
import { createVueClient } from 'feathers-pinia'
import { pinia } from './plugins/pinia'

const feathersClient = {} // See the Feathers Client install/setup pages
const api = createVueClient(feathersClient, { 
  pinia, 
  idField: '_id',
  services: {
    users: {
      setupInstance(data: ServiceInstance<Users>) {
        return useInstanceDefaults(data, { name: '' })
      },
    },
  },
})

And with the above code in place you have a default name property on every user.

Feathers Dove TS Support 🎉

Version 3 automatically uses the TypeScript enhancements when you use it with Feathers v5 Dove. Learn more about Feathers v5 Dove types in the Feathers documentation:

Big Update, Small Footprint 🐾

We kept all of the features while reducing the overall size. This means higher efficiency, with a 20% smaller footprint than the previous version. Less code means fewer bugs. 🐞

The One Correct Way 🥇

Version 3 builds from version 2's clean structure. It takes what we learned from v2's flexibility and focuses on a single, correct way to do things. There's no more confusion and no need to wonder if you're using the correct API.

Modular, Yet Centralized 📦

Building on the Feathers Client allows us to implicitly create stores only when their associated Feathers services are used. There's no need to manually create a Pinia store. There's no need to customize stores. Instead, we use Pinia store composition.

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.createInStore().

Super-Efficient SSR

SSR applications will now be especially fast. In the past, if you had an app with 30 services, you had a Pinia hydration bundle that contained 30 services, even if you only used a few of them on a page. Since stores are now created only when needed, the Pinia bundle contains only the services actually used to render the page.

Just Use Services ⭐️⭐️⭐️⭐️⭐️

You can think of the Feathers Service as the Model. The Feathers-Pinia Client now replaces the service Models and Stores APIs. All of these methods are now available directly on each Feathers Client service.

ts
const { api } = useFeathers()
const service = api.service('users')

// create data instances
service.new(data)

// api methods
service.find(params)
service.findOne(params) // unique to feathers-pinia
service.count(params)
service.get(id, params)
service.create(id, params)
service.patch(id, params)
service.remove(id, params)

// store methods
service.findInStore(params)
service.findOneInStore(params)
service.countInStore(params)
service.getFromStore(id, params)
service.createInStore(data, params) // data is a record or array or records
service.patchInStore(idData, params) // idData is one or more ids or records
service.removeFromStore(idData, params) // idData is one or more ids or records

// hybrid methods
service.useFind(params, options)
service.useGet(id, options)
service.useGetOnce(id, options)

// event methods
service.on(eventName, eventHandler)
service.emit(eventName, data)
service.removeListener(eventName, eventHandler)

The Feathers Service is the only place to find these methods, now.

Composition API Stores 🎉

Feathers-Pinia publishes a couple of Composition API utilities for creating Pinia setup stores.

useAuth

Create ultra-flexible auth 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 { api } = useFeathers()
  const auth = useAuth({ api, servicePath: 'users' })

  auth.reAuthenticate()

  return auth
})

Learn more about the new useAuth utility

useDataStore

The useDataStore utility allows creating your own data stores with the same shape as service stores. You only need this if you want to manage non-Feathers data with the same API.

Learn more about the new useDataStore utility.

Implicit Data Modeling

Data modeling is one of the most-loved features in Feathers-Pinia. Version 2.x introduced Model Functions instead of Classes. Now in v3 model functions are implicitly created for you. You can customize them by providing a setupInstance function in the individual service options.

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

Nuxt Module ⚡️

Feathers-Pinia comes with a module for Nuxt which registers auto-imports. Learn more about the new Nuxt Module

Auto-Imports ⚡️

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.

Improved Querying

All sift operators enabled

These operators are now enabled for store queries, by default:

Learn more on the Querying Data page.

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
const { api } = useFeathers()

// $like
const { data } = api.service('messages').findInStore({
  query: { text: { $like: '%Mo%' } }
})
expect(data.value.map((m) => m.id)).toEqual([1])
ts
// $notLike
const { data } = api.service('messages').findInStore({
  query: { text: { $notLike: '%Mo%' } }
})
expect(data.value.map((m) => m.id)).toEqual([2, 3, 4])
ts
// $ilike
const { data } = api.service('messages').findInStore({
  query: { text: { $ilike: '%Mo%' } }
})
expect(data.value.map((m) => m.id)).toEqual([1, 2])
ts
// $iLike
const { data } = api.service('messages').findInStore({
  query: { text: { $iLike: '%Mo%' } }
})
expect(data.value.map((m) => m.id)).toEqual([1, 2])
ts
// $notILike
const { data } = api.service('messages').findInStore({
  query: { text: { $notILike: '%Mo%' } }
})
expect(data.value.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.

Custom Local Query Operators

You can now register your own custom operators for store queries, allowing more flexibility of how to filter store data. This is done using the customSiftOperators configuration option.

See the createPiniaClient documentation.

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 { api } = useFeathers()

const task = api.service('tasks').new({ description: 'Do Something', isComplete: false })
await task.patch({ data: { isComplete: true } })

Eager Commits

Eager updates are enabled, by default, when calling patch/save on a clone. This means that commit is called before the API request goes out. If an API errors occurs, the change will be rolled back.

Sometimes eager commits aren't desirable, so you can turn them off when needed by passing { eager: false }, like this:

ts
await clone.save({ eager: false )

With eager: false, the commit will happen after the API server responds to the patch request.

Read more about FeathersModel Instances

Reactive Instances ➕

Thanks to the built-in modeling API, all instances are now always reactive, even when not added to the store.

ts
const { api } = useFeathers()
const task = api.service('tasks').new({ 
  description: 'Bind me to a template. I am ready.'
})

Handle Associations

Use the modeling utilities to define associations on your data. The new methods assure that new instances are not enumerable, so they won't be sent to the API server. The storeAssociated utility automatically distributes related data into correct service stores with a simple config.

New service.useFind API 🎁

The best parts of the former useFind and useFindWatched APIs have been combined into service.useFind. 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 Service API page.

Removals ➖

The following APIs are no longer available:

No update method

The service.update method from the Feathers Service Interface is not implemented, since service.patch can more flexibly handle the same functionality and enables nice features like patching diffing.

Service Methods out of Store

The service stores are now just data stores. There's no more fetching data through the store. Just use the Feathers Client services:

ts
const { api } = useFeathers()

api.service('contacts').findInStore({ query: { $limit: 100, $skip: 0 } })

useFeathersModel

Creating standalone model functions has been replaced by each service's new method. You can create instances by calling service.new(data).

useBaseModel

The useBaseModel utility has also been removed in favor of the service's new method.

usePagination

This composition API utility is now baked into service.useFind.

useFindWatched

This method has been removed. Use the service.useFind API, instead.

useGetWatched

This method has been removed. Use the service.useGet and service.useGetOnce APIs, instead.

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.

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