This document outlines the proper way of using plugins in a hapi-based application. By "proper" I mean "as designed". Of course you can do whatever you want in your code and there are many other patterns you can choose or develop, but this is what I had in mind when I designed the plugin system and as I continue to evolve it.
This document is a work-in-progress and only half-way done. It should probably move to a page on the hapijs.com site if someone wants to do the proofing work and submit it. I got bored writing it so will probably never finish it.
When should I organize my code using plugins?
Every hapi application should be implemented inside a plugin. This provides an easy path for extensibility and isolates your code from other plugins you are likely to use (e.g. inert for services static files, vision for template rendering, etc.). It also allows you to use more advace tools such as glue for plugin management and even rejoice for a command-line approach to server configuraiton. The overhead of implementing your server code inside a plugin is insignificant.
Should I break my application into multiple plugins?
Plugins are designed for two main purposes:
- Break a large code base into smaller pieces to facilitate easier team collaboration. Instead of having a large and complex code base that many people share and constantly change, the plugins provide a very thin layer of abstraction that allows each team to focus on their domain and to combine all the peices together for a single server deployment.
- Provide flexibility in deployment configuration. For example, a developer debug plugin (e.g. tv) can be included in development while a reporting plugin (e.g. good) is used in production. Another approach is to allow running a complex applicaiton as a single server at first, and then as it needs to scale, break out funcitonality to run on separate servers (e.g. a catalog and checkout services can be implemented as two plugins and run either on one server or two separate ones).
In addition, plugins help manage complex configuration. The plugin functionality provides various sandboxing features such as plugin-specific extensions, route prefix, view manager, and soon cascading route configuration. Using plugins to group together sets of endpoints allows applying extensions selectively only on them. This use case alone is probably not a great reason to use plugins.
If your environment matches the two use cases, you are likely to benefit from breaking your application into multiple plugins. Otherwise, you are adding complexity for little gain. The one exception to this rule is when using connectionless plugins (plugins which do not add routes or make other connection-specific calls). It usually adds very little overhead and complexity to use multiple connectionless plugins and can help organize your code.
When should I not use multiple plugins?
Don't use plugins when simple node modules are enough or when all you are trying to do is share state across your application. Breaking your application into plugins with dependencies is often a terrible idea. The plugin registration process is designed for minimal interdependencies, not for complex configuration.
Examples of bad plugins:
- open a database connection and expose it
- share a third-party API client connection
- a wrapper around existing node module to make it available as a hapi plugin
The easiest rule of bad plugin design is the need for dependencies inside your own code. Again, breaking functionality into connectionless plugins is a great pattern because connectionless plugins can be configured using the
once option and registered multiple times instead of specifying complex dependency rules. But in almost every other case, you can put the common functionality in a node module and use it instead of introducing plugin dependencies.
Another good rule is the need to use
server.expose(). In 99% of the times, it indicates poor plugin design.
How to share state within a plugin?
The reason to minimize the number of plugins you have is not just complexity but also functionality. One of the best features available to you is
server.bind(). It allows you to provide an object with all the shared state your application needs such as database connections, configuration, passwords, etc. and the make that object available within any handler, method, or extension code.
You should use
server.bind() whenever possible. However, it has one limitation - it doesn't cross plugin boundaries. You can set a global server context but once you are inside a plugin, there is no cascade and other plugins (including inner dependencies) cannot see that context object. In other words, by breaking your code into multiple plugins you are losing this great feature.
How to share state across plugins?
server.bind() cannot cross plugin boundaries and we should avoid any code outside our main applicaiton plugin, we need to use
server.app which is shared across all plugins.