UNPKG

15 kBMarkdownView Raw
1<h1 align="center">Fastify</h1>
2
3# The hitchhiker's guide to plugins
4First of all, `DON'T PANIC`!
5
6Fastify was built from the beginning to be an extremely modular system. We built a powerful API that allows you to add methods and utilities to Fastify by creating a namespace. We built a system that creates an encapsulation model that allows you to split your application in multiple microservices at any moment, without the need to refactor the entire application.
7
8**Table of contents**
9- [Register](#register)
10- [Decorators](#decorators)
11- [Hooks](#hooks)
12- [Middlewares](#middlewares)
13- [How to handle encapsulation and distribution](#distribution)
14- [Handle errors](#handle-errors)
15- [Let's start!](#start)
16
17<a name="register"></a>
18## Register
19As in JavaScript everything is an object, in Fastify everything is a plugin.<br>
20Your routes, your utilities and so on are all plugins. To add a new plugin, whatever its functionality is, in Fastify you have a nice and unique api to use: [`register`](https://github.com/fastify/fastify/blob/master/docs/Plugins.md).
21```js
22fastify.register(
23 require('./my-plugin'),
24 { options }
25)
26```
27`register` creates a new Fastify context, this means that if you do any change to the Fastify instance, those changes will not be reflected in the context's ancestors. In other words, encapsulation!
28
29
30*Why is encapsulation important?*<br>
31Well, let's say you are creating a new disruptive startup, what do you do? You create an api server with all your stuff, everything in the same place, a monolith!<br>
32Ok, you are growing very fast and you want to change your architecture and try microservices. Usually this implies a huge amount of work, because of cross dependencies and the lack of separation of concerns.<br>
33Fastify helps you a lot in this direction, because thanks to the encapsulation model it will completely avoid cross dependencies, and will help you structure your code in cohesive blocks.
34
35*Let's return to how to correctly use `register`.*<br>
36As you probably know, the required plugins must expose a single function with the following signature
37```js
38module.exports = function (fastify, options, next) {}
39```
40Where `fastify` is (pretty obvious) the encapsulated Fastify instance, `options` is the options object and `next` is the function you **must** call when your plugin is ready.
41
42Fastify's plugin model is fully reentrant and graph-based, it handles without any kind of problem asynchronous code and it guarantees the load order of the plugins, even the close order! *How?* Glad you asked, checkout [`avvio`](https://github.com/mcollina/avvio)! Fastify starts loading the plugin __after__ `.listen()`, `.inject()` or `.ready()` are called.
43
44Inside a plugin you can do whatever you want, register routes, utilities (we'll see this in a moment) and do nested registers, just remember to call `next` when everything is set up!
45```js
46module.exports = function (fastify, options, next) {
47 fastify.get('/plugin', (request, reply) => {
48 reply.send({ hello: 'world' })
49 })
50
51 next()
52}
53```
54
55Well, now you know how to use the `register` api and how it works, but how do we add new functionality to Fastify and even better, share them with other developers?
56
57<a name="decorators"></a>
58## Decorators
59Okay, let's say that you wrote an utility that is so good that you decided to make it available along all your code. How would you do it? Probably something like the following:
60```js
61// your-awesome-utility.js
62module.exports = function (a, b) {
63 return a + b
64}
65```
66```js
67const util = require('./your-awesome-utility')
68console.log(util('that is ', ' awesome'))
69```
70And now you will import your utility in every file you need it. (And don't forget that you will probably also need it in your test).
71
72Fastify offers you a way nicer and elegant way to do this, *decorators*.
73Create a decorator is extremely easy, just use the [`decorate`](https://github.com/fastify/fastify/blob/master/docs/Decorators.md) api:
74```js
75fastify.decorate('util', (a, b) => a + b)
76```
77Now you can access your utility just by doing `fastify.util` whenever you need it, even inside your test.<br>
78And here's starts the magic; do you remember that few lines above we talked about encapsulation? Well, using `register` and `decorate` in conjunction enable exactly that, let me show you an example to clarify this:
79```js
80fastify.register((instance, opts, next) => {
81 instance.decorate('util', (a, b) => a + b)
82 console.log(instance.util('that is ', ' awesome'))
83
84 next()
85})
86
87fastify.register((instance, opts, next) => {
88 console.log(instance.util('that is ', ' awesome')) // this will throw an error
89
90 next()
91})
92```
93Inside the second register call `instance.util` will throw an error, because `util` exists only inside the first register context.<br>
94Let's step back for a moment and get deeper on this: when using the `register` api you will create a new context every time and this avoids situations like the one mentioned few line above. But pay attention, the encapsulation works only for the ancestors and the brothers, but not for the sons.
95```js
96fastify.register((instance, opts, next) => {
97 instance.decorate('util', (a, b) => a + b)
98 console.log(instance.util('that is ', ' awesome'))
99
100 fastify.register((instance, opts, next) => {
101 console.log(instance.util('that is ', ' awesome')) // this will not throw an error
102 next()
103 })
104
105 next()
106})
107
108fastify.register((instance, opts, next) => {
109 console.log(instance.util('that is ', ' awesome')) // this will throw an error
110
111 next()
112})
113```
114*Take home message: if you need that an utility is available in every part of your application, pay attention that is declared at the root scope of your application. Otherwise you can use `fastify-plugin` utility as described [here](#distribution).*
115
116`decorate` is not the unique api that you can use to extend the server functionalities, you can also use `decorateRequest` and `decorateReply`.
117
118*`decorateRequest` and `decorateReply`? Why do we need them if we already have `decorate`?*<br>
119Good question, we added them to make Fastify more developer-friendly. Let's see an example:
120```js
121fastify.decorate('html', payload => {
122 return generateHtml(payload)
123})
124
125fastify.get('/html', (request, reply) => {
126 reply
127 .type('text/html')
128 .send(fastify.html({ hello: 'world' }))
129})
130```
131It works, but it can be way better!
132```js
133fastify.decorateReply('html', function (payload) {
134 this.type('text/html') // this is the 'Reply' object
135 this.send(generateHtml(payload))
136})
137
138fastify.get('/html', (request, reply) => {
139 reply.html({ hello: 'world' })
140})
141```
142
143And in the same way you can do this for the `request` object:
144```js
145fastify.decorate('getHeader', (req, header) => {
146 return req.headers[header]
147})
148
149fastify.addHook('preHandler', (request, reply, done) => {
150 request.isHappy = fastify.getHeader(request.raw, 'happy')
151 done()
152})
153
154fastify.get('/happiness', (request, reply) => {
155 reply.send({ happy: request.isHappy })
156})
157```
158Again, it works, but it can be way better!
159```js
160fastify.decorateRequest('setHeader', function (header) {
161 this.isHappy = this.headers[header]
162})
163
164fastify.decorateRequest('isHappy', false) // this will be added to the Request object prototype, yay speed!
165
166fastify.addHook('preHandler', (request, reply, done) => {
167 request.setHeader('happy')
168 done()
169})
170
171fastify.get('/happiness', (request, reply) => {
172 reply.send({ happy: request.isHappy })
173})
174```
175
176We've seen how to extend server functionality and how handle the encapsulation system, but what if you need to add a function that must be executed every time that the server "[emits](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md)" an event?
177
178<a name="hooks"></a>
179## Hooks
180You just built an amazing utility, but now you need to execute that for every request, this is what you will likely do:
181```js
182fastify.decorate('util', (request, key, value) => { request.key = value })
183
184fastify.get('/plugin1', (request, reply) => {
185 fastify.util(request, 'timestamp', new Date())
186 reply.send(request)
187})
188
189fastify.get('/plugin2', (request, reply) => {
190 fastify.util(request, 'timestamp', new Date())
191 reply.send(request)
192})
193```
194I think we all agree that this is terrible. Code repeat, awful readability and it cannot scale.
195
196So what can you do to avoid this annoying issue? Yes, you are right, use an [hook](https://github.com/fastify/fastify/blob/master/docs/Hooks.md)!<br>
197```js
198fastify.decorate('util', (request, key, value) => { request[key] = value })
199
200fastify.addHook('preHandler', (request, reply, done) => {
201 fastify.util(request, 'timestamp', new Date())
202 done()
203})
204
205fastify.get('/plugin1', (request, reply) => {
206 reply.send(request)
207})
208
209fastify.get('/plugin2', (request, reply) => {
210 reply.send(request)
211})
212```
213Now for every request you will run your utility, it is obvious that you can register as many hooks as you need.<br>
214It can happen that you want a hook that must be executed just for a subset of routes, how can you do that? Yep, encapsulation!
215
216```js
217fastify.register((instance, opts, next) => {
218 instance.decorate('util', (request, key, value) => { request[key] = value })
219
220 instance.addHook('preHandler', (request, reply, done) => {
221 instance.util(request, 'timestamp', new Date())
222 done()
223 })
224
225 instance.get('/plugin1', (request, reply) => {
226 reply.send(request)
227 })
228
229 next()
230})
231
232fastify.get('/plugin2', (request, reply) => {
233 reply.send(request)
234})
235```
236Now your hook will run just for the first route!
237
238As you probably noticed at this time, `request` and `reply` are not the standard Nodejs *request* and *response* objects, but Fastify's objects.<br>
239
240<a name="middlewares"></a>
241## Middlewares
242Fastify [supports](https://github.com/fastify/fastify/blob/master/docs/Middlewares.md) out of the box Express/Restify/Connect middlewares, this means that you can just drop-in your old code and it will work! *(faster, by the way)*<br>
243Let's say that you are arriving from Express, and you already have some Middleware that does exactly what you need, and you don't want to redo all the work.
244How we can do that? Checkout our middlewares engine, [middie](https://github.com/fastify/middie).
245```js
246const yourMiddleware = require('your-middleware')
247fastify.use(yourMiddleware)
248```
249
250<a name="distribution"></a>
251## How to handle encapsulation and distribution
252Perfect, now you know (almost) all the tools that you can use to extend Fastify. But probably there is something you noted when trying out your code.<br>
253How can you distribute your code?
254
255The preferred way to distribute a utility is to wrap all your code inside a `register`, in this way your plugin can support an asynchronous bootstrap *(since `decorate` is a synchronous api)*, in the case of a database connection for example.
256
257*Wait, what? Didn't you tell me that `register` creates an encapsulation and that what I create inside there will not be available outside?*<br>
258Yes, I said that. But what I didn't tell you, is that you can tell to Fastify to avoid this behavior, with the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module.
259```js
260const fp = require('fastify-plugin')
261const dbClient = require('db-client')
262
263function dbPlugin (fastify, opts, next) {
264 dbClient.connect(opts.url, (err, conn) => {
265 fastify.decorate('db', conn)
266 next()
267 })
268}
269
270module.exports = fp(dbPlugin)
271```
272You can also tell to `fastify-plugin` to check the installed version of Fastify, in case of you need a specific api.
273
274As we mentioned earlier, Fastify starts loading its plugins __after__ `.listen()`, `.inject()` or `.ready()` are called and as such, __after__ they have been declared. This means that, even though the plugin may inject variables to the external fastify instance via [`decorate`](https://github.com/fastify/fastify/blob/master/docs/Decorators.md), the decorated variables will not be accessible before calling `.listen()`, `.inject()` or `.ready()`.
275
276In case you rely on a variable injected by a preceding plugin and want to pass that in the `options` argument of `register`, you can do so by using a function instead of an object:
277```js
278const fastify = require('fastify')()
279const fp = require('fastify-plugin')
280const dbClient = require('db-client')
281
282function dbPlugin (fastify, opts, next) {
283 dbClient.connect(opts.url, (err, conn) => {
284 fastify.decorate('db', conn)
285 next()
286 })
287}
288
289fastify.register(fp(dbPlugin), { url: 'https://example.com' })
290fastify.register(require('your-plugin'), parent => {
291 return { connection: parent.db, otherOption: 'foo-bar' }
292})
293```
294In the above example, the `parent` variable of the function passed in as the second argument of `register` is a copy of the **external fastify instance** that the plugin was registered at. This means that we are able to access any variables that were injected by preceding plugins in the order of declaration.
295
296<a name="handle-errors"></a>
297## Handle errors
298It can happen that one of your plugins could fail during the startup. Maybe you expect it and you have a custom logic that will be triggered in that case. How can you do this?
299The `after` api is what you need. `after` simply registers a callback that will be executed just after a register, and it can take up to three parameters.<br>
300The callback changes based on the parameters your are giving:
301
3021. If no parameter is given to the callback and there is an error, that error will be passed to the next error handler.
3031. If one parameter is given to the callback, that parameter will be the error object.
3041. If two parameters are given to the callback, the first will be the error object, the second will be the done callback.
3051. If three parameters are given to the callback, the first will be the error object, the second will be the top level context unless you have specified both server and override, in that case the context will be what the override returns, and the third the done callback.
306
307Let's see how to use it:
308```js
309fastify
310 .register(require('./database-connector'))
311 .after(err => {
312 if (err) throw err
313 })
314```
315
316<a name="start"></a>
317## Let's start!
318Awesome, now you know everything you need to know about Fastify and its plugin system to start building your first plugin, and please if you do, tell us! We will add it to the [*ecosystem*](https://github.com/fastify/fastify#ecosystem) section of our documentation!
319
320If you want to see some real world example, checkout:
321- [`point-of-view`](https://github.com/fastify/point-of-view)
322Templates rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.
323- [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb)
324Fastify MongoDB connection plugin, with this you can share the same MongoDb connection pool in every part of your server.
325- [`fastify-multipart`](https://github.com/fastify/fastify-multipart)
326Multipart support for Fastify
327- [`fastify-helmet`](https://github.com/fastify/fastify-helmet) Important security headers for Fastify
328
329
330*Do you feel it's missing something here? Let us know! :)*