Nuxt 3
Learn how to setup Feathers-Pinia 3 with Nuxt.
Overview
Follow these steps to get started with a new Nuxt app:
- Create a Nuxt app
- Use the starter project and read the below as reference. OR
- Start a new Nuxt app and follow the below as instructions.
- Install packages,
- Follow the instructions, below.
Note
Note that for auto-import to work in Nuxt 3, the dev server must be running. The dev server builds the TypeScript types for you as you code, which is really convenient.
1. Feathers Client
In Nuxt, we setup the Feathers Client in a Nuxt plugin. This way, every request has its own client instance, preventing the ability to leak data between requests.
Nuxt supports Static Site Generation (SSG), Server-Side Rendering (SSR), and Hybrid Rendering (mixed rendering types). We'll setup a Feathers Client that will work in any mode. This example will use @feathersjs/rest
with fetch
on the server and @feathersjs/socketio
in the browser.
Since we need an SSR-compatible version of fetch
, we will use ofetch.
npm i ofetch -D
Next, create a file named 1.feathers.ts
in the plugins
folder. We prefix with a 1
because Nuxt plugins are run in alphabetical order. We want Feathers to load before other plugins that might use it. An example is provided for the typed Dove client and for a manually-setup client.
// plugins/1.feathers.ts
import { createClient } from 'feathers-pinia-api'
import { OFetch, createPiniaClient } from 'feathers-pinia'
// rest imports for the server
import { $fetch } from 'ofetch'
import rest from '@feathersjs/rest-client'
// socket.io imports for the browser
import socketio from '@feathersjs/socketio-client'
import io from 'socket.io-client'
/**
* Creates a Feathers Rest client for the SSR server and a Socket.io client for the browser.
* Also provides a cookie-storage adapter for JWT SSR using Nuxt APIs.
*/
export default defineNuxtPlugin(async (nuxt) => {
const host = import.meta.env.VITE_MYAPP_API_URL as string || 'http://localhost:3030'
// Store JWT in a cookie for SSR.
const storageKey = 'feathers-jwt'
const jwt = useCookie<string | null>(storageKey)
const storage = {
getItem: () => jwt.value,
setItem: (key: string, val: string) => (jwt.value = val),
removeItem: () => (jwt.value = null),
}
// Use Rest for the SSR Server and socket.io for the browser
const connection = process.server
? rest(host).fetch($fetch, OFetch)
: socketio(io(host, { transports: ['websocket'] }))
// create the feathers client
const feathersClient = createClient(connection, { storage, storageKey })
// wrap the feathers client
const api = createPiniaClient(feathersClient, {
pinia: nuxt.$pinia,
ssr: !!process.server,
idField: '_id',
whitelist: [],
paramsForServer: [],
skipGetIfExists: true,
customSiftOperators: {},
services: {},
})
return { provide: { api } }
})
// plugins/1.feathers.ts
import { type Service, feathers } from '@feathersjs/feathers'
import authenticationClient from '@feathersjs/authentication-client'
import { OFetch, createPiniaClient } from 'feathers-pinia'
// rest imports for the server
import { $fetch } from 'ofetch'
import rest from '@feathersjs/rest-client'
// socket.io imports for the browser
import socketio from '@feathersjs/socketio-client'
import io from 'socket.io-client'
// Define your custom types (usually imported from another file)
export interface Book {
_id: string
title: string
}
// Define ServiceTypes by wrapping your custom type in the `Service` type
export interface ServiceTypes {
'book': Service<Book>
}
/**
* Creates a Feathers Rest client for the SSR server and a Socket.io client for the browser.
* Also provides a cookie-storage adapter for JWT SSR using Nuxt APIs.
*/
export default defineNuxtPlugin(async (nuxt) => {
const host = import.meta.env.VITE_MYAPP_API_URL as string || 'http://localhost:3030'
// Store JWT in a cookie for SSR.
const storageKey = 'feathers-jwt'
const jwt = useCookie<string | null>(storageKey)
const storage = {
getItem: () => jwt.value,
setItem: (key: string, val: string) => (jwt.value = val),
removeItem: () => (jwt.value = null),
}
// Use Rest for the SSR Server and socket.io for the browser
const connection = process.server
? rest(host).fetch($fetch, OFetch)
: socketio(io(host, { transports: ['websocket'] }))
// create the feathers client
const feathersClient = feathers<ServiceTypes>>()
.configure(connection)
.configure(authenticationClient({ storage, storageKey }))
// wrap the feathers client
const api = createPiniaClient(feathersClient, {
pinia: nuxt.$pinia,
ssr: !!process.server,
idField: '_id',
whitelist: [],
paramsForServer: [],
skipGetIfExists: true,
customSiftOperators: {},
setupInstance(data) {
return data
},
customizeStore(defaultStore) {
return {}
},
services: {},
})
return { provide: { api } }
})
The previous code snippet utilizes Nuxt's useCookie
for SSR compatibility. If you plan to use SSG or a non-server-based rendering strategy, see SSG-Compatible localStorage on the Common Patterns page.
Also, notice the line at the end: return { provide: { api } }
. This line makes the api
available to the rest of the Nuxt application. We'll use it after we setup Pinia.
2. Pinia
Let's get Pinia installed and update the Nuxt config:
npm install pinia @pinia/nuxt
Setup your Nuxt config:
// nuxt.config.ts
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'nuxt-feathers-pinia',
],
imports: {
// Not required, but useful: list folder names here to enable additional "composables" folders.
dirs: [],
},
// Enable Nuxt Takeover Mode
typescript: {
shim: false,
},
// optional, Vue Reactivity Transform
experimental: {
reactivityTransform: true,
},
})
You can read more about the above configuration at these links:
- @pinia/nuxt module
- nuxt-feathers-pinia module
- Nuxt
imports
config - Nuxt Takeover Mode
- Vue Reactivity Transform
If you use npm
as your package manager and you see the error ERESOLVE unable to resolve dependency tree
, add this to your package.json:
{
"overrides": {
"vue": "latest"
}
}
3. useFeathers
Composable
Let's create a composable that gives us universal access to our Feathers-Pinia Client:
// composables/feathers.ts
// Provides access to Feathers clients
export function useFeathers() {
const { $api: api } = useNuxtApp()
return { api }
}
Any key returned in a Nuxt plugin's provide
object will have a $
prepended. The above example normalizes it back to api
. You can return multiple clients in this object, if desired. With the above composable in place, we can now access the Feathers client from within in components, plugins, and middleware, like this:
const { api } = useFeathers()
Auto-imports decouple our code from module paths and are super convenient. Read more about Auto-Imports in the Nuxt Module.
You're ready to start managing data with Feathers Pinia. There's no need to setup Models or Stores, since it's all done for you, implicitly. If your applications needs user login, continue to the next section.
4. Authentication
The following sections demonstrate how to implement local authentication.
Assess Your Risk
The auth examples on this page will suffice for apps with simple security requirements. If you are building an app with privacy requirements, you need something more secure.
There are multiple ways to secure your app. If you need help, please contact a FeathersHQ member for consulting services.
4.1 Auth Store
Feathers-Pinia 3.0 uses a setup
store for the auth store. The new useAuth
utility contains all of the logic for authentication in most apps. Using the composition API allows more simplicity and more flexibility for custom scenarios. We'll keep this example simple. To implement auth, create the file below:
Note about access tokens
In version 2 the useAuth
plugin does not store the accessToken
in the store, since the Feathers Client always holds a copy, which can be retrieved asynchronously. See the useAuth docs to see how to manually store the accessToken
. Keep in mind that storing your accessToken
in more places likely makes it less secure.
// stores/auth.ts
import { acceptHMRUpdate, defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', () => {
const { api } = useFeathers()
const auth = useAuth({ api, servicePath: 'users' })
return auth
})
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useAuthStore, import.meta.hot))
Notice that we've called useAuth
by providing the api
and servicePath
to the users service. By providing the servicePath
, it will automatically add a returned user
to the store after successful login.
The Auth store for Nuxt varies slightly from the Vite app. It does not call reAuthenticate
inside the store. Instead, we will call it in the next step from within the auth plugin.
4.2 Auth Plugin
Now let's create a plugin to automatically reAuthenticate
after a refresh. We'll call it 2.feathers-auth.ts
in order to make sure it runs after the Feathers-Pinia Client is ready.
// plugins/2.feathers-auth.ts
/**
* Make sure reAuthenticate finishes before we begin rendering.
*/
export default defineNuxtPlugin(async (_nuxtApp) => {
const auth = useAuthStore()
await auth.reAuthenticate()
})
4.3 Route Middleware
Now let's protect some routes. Create the following file to restrict non-authenticated users the routes in the publicRoutes
array. Authenticated users will have access to all routes.
// middleware/session.global.ts
export default defineNuxtRouteMiddleware(async (to, _from) => {
const auth = useAuthStore()
await auth.getPromise()
// Allow 404 page to show
if (!to.matched.length)
return
// if user is not logged in, redirect to '/' when not navigating to a public page.
const publicRoutes = ['/', '/login']
if (!auth.user) {
if (!publicRoutes.includes(to.path))
return navigateTo('/')
}
})
Instead of blindly redirecting to the login page, the middleware allows the 404 page to work by checking the current route for matches.
What's Next?
Learn how to query data with a Feathers-Pinia service.