Decorators
Decorators
The decorators API allows customization of the core Fastify objects, such as the server instance itself and any request and reply objects used during the HTTP request lifecycle. The decorators API can be used to attach any type of property to the core objects, e.g. functions, plain objects, or native types.
This API is a synchronous API. Attempting to define a decoration
asynchronously could result in the Fastify instance booting prior to the
decoration completing its initialization. To avoid this issue, and register an
asynchronous decoration, the register
API, in combination with
fastify-plugin
, must be used instead. To learn more, see the
Plugins documentation.
Decorating core objects with this API allows the underlying JavaScript engine to optimize handling of the server, request, and reply objects. This is accomplished by defining the shape of all such object instances before they are instantiated and used. As an example, the following is not recommended because it will change the shape of objects during their lifecycle:
// Bad example! Continue reading.
// Attach a user property to the incoming request before the request
// handler is invoked.
fastify.addHook('preHandler', function (req, reply, done) {
req.user = 'Bob Dylan'
done()
})
// Use the attached user property in the request handler.
fastify.get('/', function (req, reply) {
reply.send(`Hello, ${req.user}`)
})
Since the above example mutates the request object after it has already been instantiated, the JavaScript engine must deoptimize access to the request object. By using the decoration API this deoptimization is avoided:
// Decorate request with a 'user' property
fastify.decorateRequest('user', '')
// Update our property
fastify.addHook('preHandler', (req, reply, done) => {
req.user = 'Bob Dylan'
done()
})
// And finally access it
fastify.get('/', (req, reply) => {
reply.send(`Hello, ${req.user}!`)
})
See JavaScript engine fundamentals: Shapes and Inline Caches for more information on this topic.
Usage
decorate(name, value, [dependencies])
This method is used to customize the Fastify server instance.
For example, to attach a new method to the server instance:
fastify.decorate('utility', function () {
// Something very useful
})
As mentioned above, non-function values can be attached:
fastify.decorate('conf', {
db: 'some.db',
port: 3000
})
To access decorated properties, simply use the name provided to the decoration API:
fastify.utility()
console.log(fastify.conf.db)
The dependencies
parameter is an optional list of decorators that the
decorator being defined relies upon. This list is simply a list of string names
of other decorators. In the following example, the "utility" decorator depends
upon "greet" and "log" decorators:
fastify.decorate('utility', fn, ['greet', 'log'])
If a dependency is not satisfied, the decorate
method will throw an exception.
The dependency check is peformed before the server instance is booted. Thus,
it cannot occur during runtime.
decorateReply(name, value, [dependencies])
As the name suggests, this API is used to add new methods/properties to the core
Reply
object:
fastify.decorateReply('utility', function () {
// Something very useful
})
Note: using an arrow function will break the binding of this
to the Fastify
Reply
instance.
See decorate
for information about the dependencies
parameter.
decorateRequest(name, value, [dependencies])
As above with decorateReply
, this API is used add new
methods/properties to the core Request
object:
fastify.decorateRequest('utility', function () {
// something very useful
})
Note: using an arrow function will break the binding of this
to the Fastify
Request
instance.
See decorate
for information about the dependencies
parameter.
hasDecorator(name)
Used to check for the existence of a server instance decoration:
fastify.hasDecorator('utility')
hasRequestDecorator
Used to check for the existence of a Request decoration:
fastify.hasRequestDecorator('utility')
hasReplyDecorator
Used to check for the existence of a Reply decoration:
fastify.hasReplyDecorator('utility')
Decorators and Encapsulation
Defining a decorator (using decorate
, decorateRequest
or decorateReply
)
with the same name more than once in the same encapsulated context will
throw an exception.
As an example, the following will throw:
const server = require('fastify')()
server.decorateReply('view', function (template, args) {
// Amazing view rendering engine
})
server.get('/', (req, reply) => {
reply.view('/index.html', { hello: 'world' })
})
// Somewhere else in our codebase, we define another
// view decorator. This throws.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})
server.listen(3000)
But this will not:
const server = require('fastify')()
server.decorateReply('view', function (template, args) {
// Amazing view rendering engine.
})
server.register(async function (server, opts) {
// We add a view decorator to the current encapsulated
// plugin. This will not throw as outside of this encapsulated
// plugin view is the old one, while inside it is the new one.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})
server.get('/', (req, reply) => {
reply.view('/index.page', { hello: 'world' })
})
}, { prefix: '/bar' })
server.listen(3000)
Getters and Setters
Decorators accept special "getter/setter" objects. These objects have functions
named getter
and setter
(though, the setter
function is optional). This
allows defining properties via decorators. For example:
fastify.decorate('foo', {
getter () {
return 'a getter'
}
})
Will define the foo
property on the Fastify instance:
console.log(fastify.foo) // 'a getter'