Encapsulation

Encapsulation

A fundamental feature of Fastify is the “encapsulation context.” The encapsulation context governs which decorators, registered hooks, and plugins are available to routes. A visual representation of the encapsulation context is shown in the following figure:

Figure 1

In the above figure, there are several entities:

  1. The root context
  2. Three root plugins
  3. Two child contexts where each child context has
    • Two child plugins
    • One grandchild context where each grandchild context has
      • Three child plugins

Every child context and grandchild context has access to the root plugins. Within each child context, the grandchild contexts have access to the child plugins registered within the containing child context, but the containing child context does not have access to the child plugins registered within its grandchild context.

Given that everything in Fastify is a plugin, except for the root context, every “context” and “plugin” in this example is a plugin that can consist of decorators, hooks, plugins, and routes. Thus, to put this example into concrete terms, consider a basic scenario of a REST API server that has three routes: the first route (/one) requires authentication, the second route (/two) does not, and the third route (/three) has access to the same context as the second route. Using @fastify/bearer-auth to provide the authentication, the code for this example is as follows:

  1. 'use strict'
  2. const fastify = require('fastify')()
  3. fastify.decorateRequest('answer', 42)
  4. fastify.register(async function authenticatedContext (childServer) {
  5. childServer.register(require('@fastify/bearer-auth'), { keys: ['abc123'] })
  6. childServer.route({
  7. path: '/one',
  8. method: 'GET',
  9. handler (request, response) {
  10. response.send({
  11. answer: request.answer,
  12. // request.foo will be undefined as it's only defined in publicContext
  13. foo: request.foo,
  14. // request.bar will be undefined as it's only defined in grandchildContext
  15. bar: request.bar
  16. })
  17. }
  18. })
  19. })
  20. fastify.register(async function publicContext (childServer) {
  21. childServer.decorateRequest('foo', 'foo')
  22. childServer.route({
  23. path: '/two',
  24. method: 'GET',
  25. handler (request, response) {
  26. response.send({
  27. answer: request.answer,
  28. foo: request.foo,
  29. // request.bar will be undefined as it's only defined in grandchildContext
  30. bar: request.bar
  31. })
  32. }
  33. })
  34. childServer.register(async function grandchildContext (grandchildServer) {
  35. grandchildServer.decorateRequest('bar', 'bar')
  36. grandchildServer.route({
  37. path: '/three',
  38. method: 'GET',
  39. handler (request, response) {
  40. response.send({
  41. answer: request.answer,
  42. foo: request.foo,
  43. bar: request.bar
  44. })
  45. }
  46. })
  47. })
  48. })
  49. fastify.listen({ port: 8000 })

The above server example shows all of the encapsulation concepts outlined in the original diagram:

  1. Each child context (authenticatedContext, publicContext, and grandchildContext) has access to the answer request decorator defined in the root context.
  2. Only the authenticatedContext has access to the @fastify/bearer-auth plugin.
  3. Both the publicContext and grandchildContext have access to the foo request decorator.
  4. Only the grandchildContext has access to the bar request decorator.

To see this, start the server and issue requests:

  1. # curl -H 'authorization: Bearer abc123' http://127.0.0.1:8000/one
  2. {"answer":42}
  3. # curl http://127.0.0.1:8000/two
  4. {"answer":42,"foo":"foo"}
  5. # curl http://127.0.0.1:8000/three
  6. {"answer":42,"foo":"foo","bar":"bar"}

Sharing Between Contexts

Notice that each context in the prior example inherits only from the parent contexts. Parent contexts cannot access any entities within their descendent contexts. This default is occasionally not desired. In such cases, the encapsulation context can be broken through the usage of fastify-plugin such that anything registered in a descendent context is available to the containing parent context.

Assuming the publicContext needs access to the bar decorator defined within the grandchildContext in the previous example, the code can be rewritten as:

  1. 'use strict'
  2. const fastify = require('fastify')()
  3. const fastifyPlugin = require('fastify-plugin')
  4. fastify.decorateRequest('answer', 42)
  5. // `authenticatedContext` omitted for clarity
  6. fastify.register(async function publicContext (childServer) {
  7. childServer.decorateRequest('foo', 'foo')
  8. childServer.route({
  9. path: '/two',
  10. method: 'GET',
  11. handler (request, response) {
  12. response.send({
  13. answer: request.answer,
  14. foo: request.foo,
  15. bar: request.bar
  16. })
  17. }
  18. })
  19. childServer.register(fastifyPlugin(grandchildContext))
  20. async function grandchildContext (grandchildServer) {
  21. grandchildServer.decorateRequest('bar', 'bar')
  22. grandchildServer.route({
  23. path: '/three',
  24. method: 'GET',
  25. handler (request, response) {
  26. response.send({
  27. answer: request.answer,
  28. foo: request.foo,
  29. bar: request.bar
  30. })
  31. }
  32. })
  33. }
  34. })
  35. fastify.listen({ port: 8000 })

Restarting the server and re-issuing the requests for /two and /three:

  1. # curl http://127.0.0.1:8000/two
  2. {"answer":42,"foo":"foo","bar":"bar"}
  3. # curl http://127.0.0.1:8000/three
  4. {"answer":42,"foo":"foo","bar":"bar"}