Skip to content

Latest commit

 

History

History
347 lines (233 loc) · 17 KB

readme.md

File metadata and controls

347 lines (233 loc) · 17 KB

FAQ

We've been collecting some commonly asked questions here. We'll either be updating the guide directly, providing answers here, or both.

What Node versions does Feathers support

The latest version of Feathers and all plugins work with Node 6 and later. Node 6 will be supported until 2019-04-18 when the official support cycle ends.

The Feathers guides and applications generated by the CLI (@feathersjs/cli) use newer language features like async/await and require Node 8 or later.

How do I create custom methods?

One important thing to know about Feathers is that it only exposes the official service methods to clients. While you can add and use any service method on the server, it is not possible to expose those custom methods to clients.

Feathers is built around the REST architectural constraints and there are many good reasons for it. In general, almost anything that may require custom methods or RPC style actions can also be done either by creating a custom service or through hooks.

The benefits (like security, predictability, sending well defined real-time events) so far heavily outweighed the slight change in thinking required when conceptualizing your application logic.

Examples:

  • Send email action that does not store mail message in database.

Resources (services) don't have to be database records. It can be any kind of resource (like the current weather for a city or creating an email which will send it). Sending emails is usually done with either a separate email service:

app.use('/email', {
  create(data) {
    return sendEmail(data);
  }
})

Or in a hook.

  • Place an order in e-commerce web site. Behind the scenes, there are many records will be inserted in one transaction: order_item, order_header, voucher_tracking etc.

This is what Feathers hooks are for. When creating a new order you also have a well defined hook chain:

app.service('orders').hooks({
  before: {
    create: [
      validateData(),
      checkStock(),
      checkVoucher()
    ]
  },
  after: {
    create: [
      chargePayment(), // hook that calls `app.service('payment').create()`
      sendEmail(), // hook that calls `app.service('email').create()`
      updateStock() // Update product stock here
    ]
  }
})
  • A userService.resetPassword method

This can also be implemented as a password service that resets the password in the create method:

const crypto = require('crypto');

class PasswordService {
  create(data) {
    const userId = data.user_id;
    const userService = this.app.service('user');
    
    return userService.patch(userId, {
      passwordToken: crypto.randomBytes(48)
    }).then(user => sendEmail(user))
  }
  
  setup(app) {
    this.app = app;
  }
}

How do I do nested or custom routes?

Normally we find that they actually aren't needed and that it is much better to keep your routes as flat as possible. For example something like users/:userId/posts is - although nice to read for humans - actually not as easy to parse and process as the equivalent /posts?userId=<userid> that is already supported by Feathers out of the box. Additionaly, this will also work much better when using Feathers through websocket connections which do not have a concept of routes at all.

However, nested routes for services can still be created by registering an existing service on the nested route and mapping the route parameter to a query parameter like this:

app.use('/posts', postService);
app.use('/users', userService);

// re-export the posts service on the /users/:userId/posts route
app.use('/users/:userId/posts', app.service('posts'));

// A hook that updates `data` with the route parameter
function mapUserIdToData(context) {
  if(context.data && context.params.route.userId) {
    context.data.userId = context.params.route.userId;
  }
}

// For the new route, map the `:userId` route parameter to the query in a hook
app.service('users/:userId/posts').hooks({
  before: {
    find(context) {
      context.params.query.userId = context.params.route.userId;
    },
    create: mapUserIdToData,
    update: mapUserIdToData,
    patch: mapUserIdToData
  }  
})

Now going to /users/123/posts will call postService.find({ query: { userId: 123 } }) and return all posts for that user.

For more information about URL routing and parameters, refer to the Express chapter.

Note: URLs should never contain actions that change data (like post/publish or post/delete). This has always been an important part of the HTTP protocol and Feathers enforces this more strictly than most other frameworks. For example to publish a post you would call .patch(id, { published: true }).

I am not getting real-time events

Feathers Buzzard (@feathersjs/[email protected]) introduced a new, more secure event system that does not send real-time events by default. If you are not getting real-time events on the client, it is usually a problem with the event channel setup.

Have a look a the example at feathersjs.com, the real-time basics guide and the channels documentation. If you are migrating from a previous version, also see the channels section int the migration guide.

The generated application already sets up a channels.js file that sends events to only authenticated users by default but can be modified to your needs according the the channels documentation.

How do I do search?

This depends on the database adapter you are using. See the search querying chapter for more information.

Why am I not getting JSON errors?

If you get a plain text error and a 500 status code for errors that should return different status codes, make sure you have the express.errorHandler() from the @feathersjs/express module configured as described in the Express errors chapter.

Why am I not getting the correct HTTP error code

See the above answer.

How can I do custom methods like findOrCreate?

Custom functionality can almost always be mapped to an existing service method using hooks. For example, findOrCreate can be implemented as a before-hook on the service's get method. See this gist for an example of how to implement this in a before-hook.

How do I render templates?

Feathers works just like Express so it's the exact same. We've created a helpful little guide right here. For protecting Express views with authentication, also see this guide.

OAuth is not setting the cookie

If you are authenticating via oAuth but your API and frontend reside on different domains the cookie used by the authentication client can not be set. Instead, a query string redirect has to be used as shown in this gist.

How do I create channels or rooms

In Feathers channels are the way to send real-time events to only certain clients.

How do I do validation?

If your database/ORM supports a model or schema (ie. Mongoose or Sequelize) then you have 2 options.

The preferred way

You perform validation at the service level using hooks. This is better because it keeps your app database agnostic so you can easily swap databases without having to change your validations much.

If you write a bunch of small hooks that validate specific things it is easier to test and also slightly more performant because you can exit out of the validation chain early instead of having to go all the way to the point of inserting data into the database to find out if that data is invalid.

If you don't have a model or schema then validating with hooks is currently your only option. If you come up with something different feel free to submit a PR!

The ORM way

With ORM adapters you can perform validation at the model level:

The nice thing about the model level validations is Feathers will return the validation errors to the client in a nice consistent format for you.

How do I do associations?

Similar to validation, it depends on if your database/ORM supports models or not.

The preferred way

For any of the feathers database/ORM adapters you can just use hooks to fetch data from other services.

This is a better approach because it keeps your application database agnostic and service oriented. By referencing the services (using app.service().find(), etc.) you can still decouple your app and have these services live on entirely separate machines or use entirely different databases without having to change any of your fetching code. We show how to associate data in a hook in the chat guide. An alternative is the populate hook in feathers-hooks-common.

The ORM way

With mongoose you can use the $populate query param to populate nested documents.

// Find Hulk Hogan and include all the messages he sent
app.service('user').find({
  query: {
    name: '[email protected]',
    $populate: ['sentMessages']
  }
});

With Sequelize you can do this:

// Find Hulk Hogan and include all the messages he sent
app.service('user').find({
  name: '[email protected]',
  sequelize: {
    include: [{
      model: Message,
      where: { sender: Sequelize.col('user.id') }
    }]
  }
});

Or set it in a hook as described here.

Sequelize models and associations

If you are using the Sequelize adapter, understanding SQL and Sequelize first is very important. See the associations section in the feathers-sequelize documentation for more information on how to associate models using the Sequelize Feathers adapter.

What about Koa/Hapi/X?

There are many other Node server frameworks out there like Koa, a "next generation web framework for Node.JS" using ES6 generator functions instead of Express middleware or HapiJS etc. Currently, Feathers is framework independent but only provides an Express integration for HTTP APIs. More frameworks may be supported in the future with direct Node HTTP being the most likely.

How do I access the request object in hooks or services?

In short, you shouldn't need to. If you look at the hooks chapter you'll see all the params that are available on a hook.

If you still need something from the request object (for example, the requesting IP address) you can simply tack it on to the req.feathers object as described here.

How do I mount sub apps?

It's pretty much exactly the same as Express. More information can be found here.

How do I do some processing after sending the response to the user?

The hooks workflow allows you to handle these situations quite gracefully. It depends on the promise that you return in your hook. Here's an example of a hook that sends an email, but doesn't wait for a success message.

function (context) {
  
  // Send an email by calling to the email service.
  context.app.service('emails').create({
    to: '[email protected]',
    body: 'You are so great!'
  });
  
  // Send a message to some logging service.
  context.app.service('logging').create(context.data);
  
  // Return a resolved promise to immediately move to the next hook
  // and not wait for the two previous promises to resolve.
  return Promise.resolve(context);
}

How do I debug my app

It's really no different than debugging any other NodeJS app but you can refer to this blog post for more Feathers specific tips and tricks.

possible EventEmitter memory leak detected warning

This warning is not as bad as it sounds. If you got it from Feathers you most likely registered more than 64 services and/or event listeners on a Socket. If you don't think there are that many services or event listeners you may have a memory leak. Otherwise you can increase the number in the Socket.io configuration via io.sockets.setMaxListeners(number) and with Primus via primus.setMaxListeners(number). number can be 0 for unlimited listeners or any other number of how many listeners you'd expect in the worst case.

Why can't I pass params from the client?

When you make a call like:

const params = { foo: 'bar' };
client.service('users').patch(1, { admin: true }, params).then(result => {
  // handle response
});

on the client the context.params object will only be available in your client side hooks. It will not be provided to the server. The reason for this is because context.params on the server usually contains information that should be server-side only. This can be database options, whether a request is authenticated, etc. If we passed those directly from the client to the server this would be a big security risk. Only the client side context.params.query and context.params.headers objects are provided to the server.

If you need to pass info from the client to the server that is not part of the query you need to add it to context.params.query on the client side and explicitly pull it out of context.params.query on the server side. This can be achieved like so:

// client side
client.hooks({
  before: {
    all: [
      context => {
        context.params.query.$client = {
          platform: 'ios',
          version: '1.0'
        };
        
        return context;
      }
    ]
  }
});

// server side, inside app.hooks.js
const hooks = require('feathers-hooks-common');

module.exports = {
  before: {
    all: [
      // remove values from context.params.query.$client and move them to context.params
      // so context.params.query.$client.version -> context.params.version
      // and context.params.query.$client is removed.
      hooks.client('version', 'platform')
    ]
  }
}

Why are queries with arrays failing?

If you are using REST and queries with larger arrays (more than 21 items to be exact) are failing you are probably running into an issue with the querystring module which limits the size of arrays to 21 items by default. The recommended solution is to implement a custom query string parser function via app.set('query parser', parserFunction) with the arrayLimit option set to a higher value:

var qs = require('qs');

app.set('query parser', function (str) {
  return qs.parse(str, {
    arrayLimit: 100
  });
});

For more information see the Express application settings @feathersjs/rest#88 and feathers-mongoose#205.

I always get a 404 for my custom middleware

Just like in Express itself, the order of middleware matters. If you registered a custom middleware outside of the generator, you have to make sure that it runs before the notFound() error midlleware.

How do I get OAuth working across different domains

The standard Feathers oAuth setup sets the JWT in a cookie which can only be passed between the same domain. If your frontend is running on a different domain you will have to use query string redirects as outlined in this Gist.

My configuration isn't loaded

If you are running or requiring the Feathers app from a different folder Feathers configuration needs to be instructed where the configuration files for the app are located. Since it uses node-config this can be done by setting the NODE_CONFIG_DIR envorinment variable.

How do I set up HTTPS?

Check out the Feathers Express HTTPS docs.

Is Feathers production ready?

Yes! It's being used in production by a bunch of companies from startups to fortune 500s. For some more details see this answer on Quora.