Skip to content

Commit 9d8ea8d

Browse files
committed
2 parents 95be6eb + f94277e commit 9d8ea8d

File tree

9 files changed

+581
-39
lines changed

9 files changed

+581
-39
lines changed

docs/common-patterns.md

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,288 @@ export default new Vuex.Store({
507507
## Enable Debug Logging
508508

509509
If items aren't not getting added to the store properly, try setting the `debug` option on the `makeServicePlugin` to `true`. It enables some additional logging that may be useful for troubleshooting.
510+
511+
## Full nuxt example
512+
513+
In this example we will create a nuxt configuration with all the features discribed in the [Nuxt Section](./nuxt.md).
514+
515+
Our application is hosted in 2 different endpoints, one for the feathers api and another with our nuxt SSR, and we will also implement a permission system that will prevent users without an `admin` permission to get into some pages. For that you will need to [customize your payload](https://docs.feathersjs.com/cookbook/authentication/stateless.html#customizing-the-payload) in your feathers api.
516+
517+
```js
518+
// nuxt.config.js
519+
export default {
520+
env: {
521+
API_URL: process.env.API_URL || 'http://localhost:3030'
522+
},
523+
router: {
524+
middleware: [
525+
'feathers'
526+
]
527+
},
528+
plugins: [
529+
{ src: '~/plugins/feathers-vuex.js' }
530+
],
531+
modules: [
532+
'nuxt-client-init-module'
533+
],
534+
build: {
535+
transpile: [
536+
'feathers-vuex'
537+
]
538+
}
539+
}
540+
```
541+
542+
The `feathers` middleware will redirect any user in a non public page to the login, but will also redirect a loged user away from any public pages.
543+
544+
```js
545+
// ~/middleware/feathers.js
546+
export default function ({ store, redirect, route }) {
547+
const { auth } = store.state
548+
if (auth.publicPages.length > 0 && !auth.publicPages.includes(route.name) && !auth.payload) {
549+
return redirect('login')
550+
} else if (auth.publicPages.length > 0 && auth.publicPages.includes(route.name) && auth.payload) {
551+
return redirect('feed')
552+
}
553+
}
554+
```
555+
556+
The `admin` middleware will redirect any user that is not loged in or do not have the `admin` permission to a `not-permited` page.
557+
558+
```js
559+
// ~/middleware/admin.js
560+
export default function ({ store, redirect }) {
561+
const { auth } = store.state
562+
const permissions = auth.payload.permissions
563+
if (
564+
!auth.payload ||
565+
!permissions.includes('admin')
566+
) {
567+
return redirect('/not-permited')
568+
}
569+
}
570+
```
571+
Add the `admin` middleware to the administrative pages
572+
573+
```js
574+
// ~/pages/**
575+
export default {
576+
577+
...
578+
579+
middleware: ['admin']
580+
581+
...
582+
583+
}
584+
```
585+
586+
In the feathers client configuration we will use a different transport for the nuxt-server > api comunication and the nuxt-client > api. When we are making request on the server we do not need the socket io realtime messaging system so we can use an rest configuration for better performance.
587+
588+
```js
589+
// ~/plugins/feathers.js
590+
import feathers from '@feathersjs/feathers'
591+
import rest from '@feathersjs/rest-client'
592+
import axios from 'axios'
593+
import socketio from '@feathersjs/socketio-client'
594+
import auth from '@feathersjs/authentication-client'
595+
import io from 'socket.io-client'
596+
import { CookieStorage } from 'cookie-storage'
597+
import { iff, discard } from 'feathers-hooks-common'
598+
import feathersVuex, { initAuth, hydrateApi } from 'feathers-vuex'
599+
// Get the api url from the environment variable
600+
const apiUrl = process.env.API_URL
601+
let socket
602+
let restClient
603+
// We won't use socket to comunicate from server to server
604+
if (process.client) {
605+
socket = io(apiUrl, { transports: ['websocket'] })
606+
} else {
607+
restClient = rest(apiUrl)
608+
}
609+
const transport = process.client ? socketio(socket) : restClient.axios(axios)
610+
const storage = new CookieStorage()
611+
612+
const feathersClient = feathers()
613+
.configure(transport)
614+
.configure(auth({ storage }))
615+
.hooks({
616+
before: {
617+
all: [
618+
iff(
619+
context => ['create', 'update', 'patch'].includes(context.method),
620+
discard('__id', '__isTemp')
621+
)
622+
]
623+
}
624+
})
625+
626+
export default feathersClient
627+
628+
// Setting up feathers-vuex
629+
const { makeServicePlugin, makeAuthPlugin, BaseModel, models, FeathersVuex } = feathersVuex(
630+
feathersClient,
631+
{
632+
serverAlias: 'api', // optional for working with multiple APIs (this is the default value)
633+
idField: '_id', // Must match the id field in your database table/collection
634+
whitelist: ['$regex', '$options'],
635+
enableEvents: process.client // Prevent memory leak
636+
}
637+
)
638+
639+
export { makeAuthPlugin, makeServicePlugin, BaseModel, models, FeathersVuex, initAuth, hydrateApi }
640+
```
641+
642+
I prefere install the `FeathersVuex` plugin in a separate file, it's more consistent with nuxt patterns.
643+
644+
```js
645+
// ~/plugins/feathers-vuex.js
646+
import Vue from 'vue'
647+
import { FeathersVuex } from './feathers'
648+
649+
Vue.use(FeathersVuex)
650+
```
651+
652+
Configure any service you want on `~/store/services/*.js`.
653+
654+
```js
655+
// ~/store/services/users.js
656+
import feathersClient, { makeServicePlugin, BaseModel } from '~/plugins/feathers'
657+
658+
class User extends BaseModel {
659+
constructor(data, options) {
660+
super(data, options)
661+
}
662+
// Required for $FeathersVuex plugin to work after production transpile.
663+
static modelName = 'User'
664+
// Define default properties here
665+
static instanceDefaults() {
666+
return {
667+
email: '',
668+
password: '',
669+
permissions: []
670+
}
671+
}
672+
}
673+
const servicePath = 'users'
674+
const servicePlugin = makeServicePlugin({
675+
Model: User,
676+
service: feathersClient.service(servicePath),
677+
servicePath
678+
})
679+
680+
// Setup the client-side Feathers hooks.
681+
feathersClient.service(servicePath).hooks({
682+
before: {
683+
all: [],
684+
find: [],
685+
get: [],
686+
create: [],
687+
update: [],
688+
patch: [],
689+
remove: []
690+
},
691+
after: {
692+
all: [],
693+
find: [],
694+
get: [],
695+
create: [],
696+
update: [],
697+
patch: [],
698+
remove: []
699+
},
700+
error: {
701+
all: [],
702+
find: [],
703+
get: [],
704+
create: [],
705+
update: [],
706+
patch: [],
707+
remove: []
708+
}
709+
})
710+
711+
export default servicePlugin
712+
```
713+
714+
Create your nuxt store with the right nuxt pattern, exporting an Vuex store will be deprecated on nuxt 3.
715+
716+
```js
717+
// ~/store/index.js
718+
import { makeAuthPlugin, initAuth, hydrateApi, models } from '~/plugins/feathers'
719+
const auth = makeAuthPlugin({
720+
userService: 'users',
721+
state: {
722+
publicPages: [
723+
'login',
724+
'signup'
725+
]
726+
},
727+
actions: {
728+
onInitAuth ({ state, dispatch }, payload) {
729+
if (payload) {
730+
dispatch('authenticate', { strategy: 'jwt', accessToken: state.accessToken })
731+
.then((result) => {
732+
// handle success like a boss
733+
console.log('loged in')
734+
})
735+
.catch((error) => {
736+
// handle error like a boss
737+
console.log(error)
738+
})
739+
}
740+
}
741+
}
742+
})
743+
744+
const requireModule = require.context(
745+
// The path where the service modules live
746+
'./services',
747+
// Whether to look in subfolders
748+
false,
749+
// Only include .js files (prevents duplicate imports`)
750+
/.js$/
751+
)
752+
const servicePlugins = requireModule
753+
.keys()
754+
.map(modulePath => requireModule(modulePath).default)
755+
756+
export const modules = {
757+
// Custom modules
758+
}
759+
760+
export const state = () => ({
761+
// Custom state
762+
})
763+
764+
export const mutations = {
765+
// Custom mutations
766+
}
767+
768+
export const actions = {
769+
// Custom actions
770+
nuxtServerInit ({ commit, dispatch }, { req }) {
771+
return initAuth({
772+
commit,
773+
dispatch,
774+
req,
775+
moduleName: 'auth',
776+
cookieName: 'feathers-jwt'
777+
})
778+
},
779+
nuxtClientInit ({ state, dispatch, commit }, context) {
780+
781+
hydrateApi({ api: models.api })
782+
783+
if (state.auth.accessToken) {
784+
return dispatch('auth/onInitAuth', state.auth.payload)
785+
}
786+
}
787+
}
788+
789+
export const getters = {
790+
// Custom getters
791+
}
792+
793+
export const plugins = [ ...servicePlugins, auth ]
794+
```

docs/model-classes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ One important note, the `isAdmin` attribute is specified in the above example in
146146

147147
A new `setupinstance` class method is now available in version 2.0. This method allows you to transform the data and setup the final instance based on incoming data. For example, you can access the `models` object to reference other service Model classes and create data associations.
148148

149-
The function will be called with the following arguments and should return an object of default properties for new instances.
149+
The function will be called during model instance construction with the following arguments and should return an object containing properties that'll be merged into the new instance.
150150

151151
- `data {Object}` - The instance data
152152
- A `utils` object containing these props:
@@ -160,7 +160,7 @@ For an example of how you might use `setupInstance`, suppose we have two service
160160
setupInstance(data, { store, models }) {
161161
if (data.posts) {
162162
// Turn posts into an array of Post instances
163-
data.posts = data.posts.map(p => new models.Todo(p))
163+
data.posts = data.posts.map(post => new models.Post(post))
164164
}
165165
return data
166166
}

0 commit comments

Comments
 (0)