Skip to content

Instantly share code, notes, and snippets.

@bsmerbeck
Last active October 11, 2019 19:42
Show Gist options
  • Save bsmerbeck/ec905221176d2402ca1aaf10c54290b9 to your computer and use it in GitHub Desktop.
Save bsmerbeck/ec905221176d2402ca1aaf10c54290b9 to your computer and use it in GitHub Desktop.
@feathersjs v4 migration

Table of Contents

  1. Packages Before and After Migration
  2. Relevant Stack
  3. Upgrade and Modifications to Configuration
    1. Config Changes
  4. Authentication Modifications
  5. Validation
    1. Client Validation
    2. Server Validation

1. Packages Before and After Migration

feathers packages pre-migration

   "@feathersjs/authentication": "^2.1.13",
    "@feathersjs/authentication-jwt": "^2.0.7",
    "@feathersjs/authentication-local": "^1.2.7",
    "@feathersjs/client": "^3.7.8",
    "@feathersjs/configuration": "^2.0.4",
    "@feathersjs/errors": "^3.3.4",
    "@feathersjs/express": "^1.2.7",
    "@feathersjs/feathers": "^3.3.1",
    "@feathersjs/socketio": "^3.2.7",
    "@feathersjs/socketio-client": "^1.2.1"

ecosystem packages before

    "feathers-authentication-hooks": "^0.3.1",
    "feathers-blob": "^2.1.0",
    "feathers-hooks-common": "^4.20.7",
    "feathers-hooks-validate-joi": "^2.0.0",
    "feathers-knex": "^5.0.5",
    "feathers-objection": "^3.2.1",
    "feathers-permissions": "^0.2.1",
    "feathers-redux": "^3.0.0",
    "feathers-reduxify-authentication": "^1.0.1"

updated feathers packages

    "@feathersjs/authentication": "^4.3.0",
    "@feathersjs/authentication-local": "^4.3.0",
    "@feathersjs/authentication-oauth": "^4.3.0",
    "@feathersjs/client": "^4.3.5",
    "@feathersjs/configuration": "^4.3.0",
    "@feathersjs/errors": "^4.3.0",
    "@feathersjs/express": "^4.3.0",
    "@feathersjs/feathers": "^4.3.0",
    "@feathersjs/socketio": "^4.3.0",
    "@feathersjs/socketio-client": "^4.3.5",

updated ecosystem packages

    "feathers-hooks-validate-joi": "https://github.com/asamolion/feathers-hooks-validate-joi",
    "feathers-knex": "^7.1.0",
    "feathers-objection": "^4.3.0",

2. Relevant Stack

  • Data
    • DB
      • postgresql
    • Query Builder / ORM
      • knex/objection
  • Backend
    • node
    • Transports
      • socket.io (websockets)
      • express (REST)
  • API
    • feathersjs
  • Frontend
    • React
    • Redux
  • Server/Client Validation
    • @hapi/joi

3. Upgrade and Modifications to Configuration

  1. Upgrade @feathersjs/cli - straightforward and useful for the migration
  2. Run feathers upgrade

i. Config Changes

This is largely due to some syntax changes.

before

authentication: {
    secret: process.env.AUTH_SECRET,
    strategies: process.env.AUTH_STRATEGIES.split(','),
    header: process.env.AUTH_HEADER,
    path: process.env.AUTH_PATH,
    service: process.env.AUTH_SERVICE,
    jwt: {
      header: {
        typ: process.env.AUTH_JWT_HEADER
      },
      audience: process.env.AUTH_JWT_AUDIENCE,
      subject: process.env.AUTH_JWT_SUBJECT,
      issuer: process.env.AUTH_JWT_ISSUER,
      algorithm: process.env.AUTH_JWT_ALGORITHM,
      expiresIn: process.env.AUTH_JWT_EXPIRESIN,
    },
    local: {
      entity: process.env.AUTH_LOCAL_ENTITY,
      service: process.env.AUTH_LOCAL_SERVICE,
      usernameField: process.env.AUTH_LOCAL_USERNAMEFIELD,
      passwordField: process.env.AUTH_LOCAL_PASSWORDFIELD,
    }
  }

after

  • entity and service have moved into the parent authentication objection
  • strategies renamed to authStrategies
  • jwt renamed to jwtOptions
  • subject removed from jwtOptions

NOTE all of these changes are listed in the official documentation

authentication: {
    secret: process.env.AUTH_SECRET,
    authStrategies: process.env.AUTH_STRATEGIES.split(','),
    header: process.env.AUTH_HEADER,
    path: process.env.AUTH_PATH,
    service: process.env.AUTH_SERVICE,
    entity: process.env.AUTH_LOCAL_ENTITY,
    jwtOptions: {
      header: {
        typ: process.env.AUTH_JWT_HEADER
      },
      audience: process.env.AUTH_JWT_AUDIENCE,
      issuer: process.env.AUTH_JWT_ISSUER,
      algorithm: process.env.AUTH_JWT_ALGORITHM,
      expiresIn: process.env.AUTH_JWT_EXPIRESIN,
    },
    local: {
      usernameField: process.env.AUTH_LOCAL_USERNAMEFIELD,
      passwordField: process.env.AUTH_LOCAL_PASSWORDFIELD,
    }
  }

4. Authentication Modifications

The authentication service largely remained unchanged after using the upgrade tool. However, there were necessary modifications to make the service compatible with feathers-reduxify-authentication

The changes were made to after:create due to feathers-reduxify-authentication needing the user object in a specific location to manage it within the redux store.

  app.service('authentication').hooks({
    after: {
      create: [
        context => {
          context.result.data = context.result.user;
          delete context.data.password;
          return context;
        }
      ]
    }
  });

5. Validation

Validation was the most affected by the change, and at this time the official feathers-hooks-validate-joi is not compatible with v4 of feathersjs. This is due to the deprecation of the next() function, as well as some rearranging of context.

A compatible fork is available at:

https://github.com/asamolion/feathers-hooks-validate-joi

After installing this forked version, I also upgraded @hapi/joi. This created some unexpected issues. The following modifications were performed:

  1. Modification of all schemas

Schemas now require and additional Joi.Object() or `Joi.Object().keys({})

before

const schema = {
  username: Joi.string()
    .trim()
    .alphanum()
    .min(5),
  password: Joi.string()
    .trim()
    .alphanum()
    .min(8)
};

after

const schema = Joi.object({
  username: Joi.string()
    .trim()
    .alphanum()
    .min(5),
  password: Joi.string()
    .trim()
    .alphanum()
    .min(8)
});

i. Client Validation

While I was using Joi.Object.keys({schema}) for server-side validation. Client validation was not. Probably just a mistake on my own part. Also, in a helper I use for redux-form, there was a required change to include an undefined check in logic.

Form validation is handled like so

createValidator.js

export default function createValidator(schema) {
  return values => {
    const result = schema.validate(values, {
      abortEarly: false,
      stripUnknown: { objects: true }
    });
    if (result.error === null || result.error === undefined) {
      return result;
    }

    const errors = result.error.details.reduce((all, cur) => {
      const allErrors = Object.assign({}, all);
      const path = cur.path[cur.path.length - 1];
      const message = cur.message;
      if (Object.prototype.hasOwnProperty.call(allErrors, path)) {
        allErrors[path].push(message);
      } else {
        allErrors[path] = [message];
      }
      return allErrors;
    }, {});

    return errors;
  };
}

loginContainer.js

(relevant portions shown)

import createValidator from '../../../common/helpers/createValidator';
import Joi from '@hapi/joi'

const schema = Joi.object({
  username: Joi.string()
    .trim()
    .alphanum()
    .min(5),
  password: Joi.string()
    .trim()
    .alphanum()
    .min(8)
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(
  // decorate react component with redux-form
  reduxForm({
    form: 'UserSignIn',
    validate: createValidator(schema),
    onSubmitFail: onSubmitFail,
    onSubmitSuccess: onSubmitSuccess,
    onSubmit: handleSubmit
  })(LoginForm)
);

In doing this, redux-form is able to use @hapi/joi for validation of the form in real-time.

ii. Server Validation

I ran into a couple errors wit using @hapi/joi on the backend of my application as well. This was due to a weird side-effect that, when using convert: true within feathers-hooks-validate-joi, context.data was getting a nested value property.

The solution is fairly simple:

server/services/events/event.hooks/js

(relevant portion shown)

module.exports = {
  before: {
    create: [
      ...eventsValidation,
      context => {
        context.data = context.data.value;
        return context;
      }
    ]
  }
}

A simple change, but it worked for the cases where I was using convert.

So that's basically it. I may have missed some notes, and if so reach out! Hope this helps with people's v4 migrations.

-Brenden

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment