UNPKG

15.3 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- [ESM support](#esm-support)
15- [Handle errors](#handle-errors)
16- [Let's start!](#start)
17
18<a name="register"></a>
19## Register
20As with JavaScript, where everything is an object, in Fastify everything is a plugin.<br>
21Your routes, your utilities and so on are all plugins. To add a new plugin, whatever its functionality may be, in Fastify you have a nice and unique API: [`register`](https://github.com/fastify/fastify/blob/master/docs/Plugins.md).
22```js
23fastify.register(
24 require('./my-plugin'),
25 { options }
26)
27```
28`register` creates a new Fastify context, which means that if you perform any changes on the Fastify instance, those changes will not be reflected in the context's ancestors. In other words, encapsulation!
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 a lack of separation of concerns in the codebase.<br>
33Fastify helps you in that regard. Thanks to the encapsulation model it will completely avoid cross dependencies, and will help you structure your code into 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, done) {}
39```
40Where `fastify` is the encapsulated Fastify instance, `options` is the options object and `done` is the function you **must** call when your plugin is ready.
41
42Fastify's plugin model is fully reentrant and graph-based, it handles asynchronous code without any problems and it enforces both the load and close order of plugins. *How?* Glad you asked, check out [`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 `done` when everything is set up!
45```js
46module.exports = function (fastify, options, done) {
47 fastify.get('/plugin', (request, reply) => {
48 reply.send({ hello: 'world' })
49 })
50
51 done()
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 a utility that is so good that you decided to make it available along with 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 in. (And don't forget that you will probably also need it in your tests).
71
72Fastify offers you a more elegant and comfortable way to do this, *decorators*.
73Creating 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 calling `fastify.util` whenever you need it - even inside your test.<br>
78And here starts the magic; do you remember how just now we were talking 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, done) => {
81 instance.decorate('util', (a, b) => a + b)
82 console.log(instance.util('that is ', 'awesome'))
83
84 done()
85})
86
87fastify.register((instance, opts, done) => {
88 console.log(instance.util('that is ', 'awesome')) // This will throw an error
89
90 done()
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 dig deeper into this: every time you use the `register` API, a new context is created which avoids the negative situations mentioned above.
95
96Do note that encapsulation applies to the ancestors and siblings, but not the children.
97```js
98fastify.register((instance, opts, done) => {
99 instance.decorate('util', (a, b) => a + b)
100 console.log(instance.util('that is ', 'awesome'))
101
102 fastify.register((instance, opts, done) => {
103 console.log(instance.util('that is ', 'awesome')) // This will not throw an error
104 done()
105 })
106
107 done()
108})
109
110fastify.register((instance, opts, done) => {
111 console.log(instance.util('that is ', 'awesome')) // This will throw an error
112
113 done()
114})
115```
116*Take home message: if you need an utility which is available in every part of your application, take care that it's declared in the root scope of your application. If that's not an option you can use the `fastify-plugin` utility as described [here](#distribution).*
117
118`decorate` is not the only API that you can use to extend the server functionality, you can also use `decorateRequest` and `decorateReply`.
119
120*`decorateRequest` and `decorateReply`? Why do we need them if we already have `decorate`?*<br>
121Good question, we added them to make Fastify more developer-friendly. Let's see an example:
122```js
123fastify.decorate('html', payload => {
124 return generateHtml(payload)
125})
126
127fastify.get('/html', (request, reply) => {
128 reply
129 .type('text/html')
130 .send(fastify.html({ hello: 'world' }))
131})
132```
133It works, but it could be way better!
134```js
135fastify.decorateReply('html', function (payload) {
136 this.type('text/html') // This is the 'Reply' object
137 this.send(generateHtml(payload))
138})
139
140fastify.get('/html', (request, reply) => {
141 reply.html({ hello: 'world' })
142})
143```
144
145And in the same way you can do this for the `request` object:
146```js
147fastify.decorate('getHeader', (req, header) => {
148 return req.headers[header]
149})
150
151fastify.addHook('preHandler', (request, reply, done) => {
152 request.isHappy = fastify.getHeader(request.raw, 'happy')
153 done()
154})
155
156fastify.get('/happiness', (request, reply) => {
157 reply.send({ happy: request.isHappy })
158})
159```
160Again, it works, but it can be way better!
161```js
162fastify.decorateRequest('setHeader', function (header) {
163 this.isHappy = this.headers[header]
164})
165
166fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!
167
168fastify.addHook('preHandler', (request, reply, done) => {
169 request.setHeader('happy')
170 done()
171})
172
173fastify.get('/happiness', (request, reply) => {
174 reply.send({ happy: request.isHappy })
175})
176```
177
178We've seen how to extend server functionality and how to handle the encapsulation system, but what if you need to add a function that must be executed every time when the server "[emits](https://github.com/fastify/fastify/blob/master/docs/Lifecycle.md)" an event?
179
180<a name="hooks"></a>
181## Hooks
182You just built an amazing utility, but now you need to execute that for every request, this is what you will likely do:
183```js
184fastify.decorate('util', (request, key, value) => { request[key] = value })
185
186fastify.get('/plugin1', (request, reply) => {
187 fastify.util(request, 'timestamp', new Date())
188 reply.send(request)
189})
190
191fastify.get('/plugin2', (request, reply) => {
192 fastify.util(request, 'timestamp', new Date())
193 reply.send(request)
194})
195```
196I think we all agree that this is terrible. Repeated code, awful readability and it cannot scale.
197
198So what can you do to avoid this annoying issue? Yes, you are right, use a [hook](https://github.com/fastify/fastify/blob/master/docs/Hooks.md)!<br>
199```js
200fastify.decorate('util', (request, key, value) => { request[key] = value })
201
202fastify.addHook('preHandler', (request, reply, done) => {
203 fastify.util(request, 'timestamp', new Date())
204 done()
205})
206
207fastify.get('/plugin1', (request, reply) => {
208 reply.send(request)
209})
210
211fastify.get('/plugin2', (request, reply) => {
212 reply.send(request)
213})
214```
215Now for every request, you will run your utility. Obviously you can register as many hooks as you need.<br>
216Sometimes you want a hook that should be executed for just a subset of routes, how can you do that? Yep, encapsulation!
217
218```js
219fastify.register((instance, opts, done) => {
220 instance.decorate('util', (request, key, value) => { request[key] = value })
221
222 instance.addHook('preHandler', (request, reply, done) => {
223 instance.util(request, 'timestamp', new Date())
224 done()
225 })
226
227 instance.get('/plugin1', (request, reply) => {
228 reply.send(request)
229 })
230
231 done()
232})
233
234fastify.get('/plugin2', (request, reply) => {
235 reply.send(request)
236})
237```
238Now your hook will run just for the first route!
239
240As you probably noticed by now, `request` and `reply` are not the standard Nodejs *request* and *response* objects, but Fastify's objects.<br>
241
242<a name="middleware"></a>
243## Middleware
244Fastify [supports](https://github.com/fastify/fastify/blob/master/docs/Middleware.md) Express/Restify/Connect middleware out-of-the-box, which means that you can just drop-in your old code and it will work! *(faster, by the way)*<br>
245Let's say that you are arriving from Express, and you already have some Middleware which does exactly what you need, and you don't want to redo all the work.
246How we can do that? Check out our middleware engine, [middie](https://github.com/fastify/middie).
247```js
248const yourMiddleware = require('your-middleware')
249fastify.use(yourMiddleware)
250```
251
252<a name="distribution"></a>
253## How to handle encapsulation and distribution
254Perfect, now you know (almost) all of the tools that you can use to extend Fastify. But chances are that you came across one big issue: how is distribution handled?
255
256The preferred way to distribute a utility is to wrap all your code inside a `register`, in this way your plugin can support asynchronous bootstrapping *(since `decorate` is a synchronous API)*, in the case of a database connection for example.
257
258*Wait, what? Didn't you tell me that `register` creates an encapsulation and that the stuff I create inside will not be available outside?*<br>
259Yes, I said that. But what I didn't tell you, is that you can tell to Fastify to avoid this behaviour, with the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module.
260```js
261const fp = require('fastify-plugin')
262const dbClient = require('db-client')
263
264function dbPlugin (fastify, opts, done) {
265 dbClient.connect(opts.url, (err, conn) => {
266 fastify.decorate('db', conn)
267 done()
268 })
269}
270
271module.exports = fp(dbPlugin)
272```
273You can also tell `fastify-plugin` to check the installed version of Fastify, in case you need a specific API.
274
275As 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()`.
276
277In 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:
278```js
279const fastify = require('fastify')()
280const fp = require('fastify-plugin')
281const dbClient = require('db-client')
282
283function dbPlugin (fastify, opts, done) {
284 dbClient.connect(opts.url, (err, conn) => {
285 fastify.decorate('db', conn)
286 done()
287 })
288}
289
290fastify.register(fp(dbPlugin), { url: 'https://example.com' })
291fastify.register(require('your-plugin'), parent => {
292 return { connection: parent.db, otherOption: 'foo-bar' }
293})
294```
295In 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.
296
297<a name="esm-support"></a>
298## ESM support
299
300ESM is supported as well from [Node.js `v13.3.0`](https://nodejs.org/api/esm.html) and above! Just export your plugin as ESM module and you are good to go!
301
302```js
303// plugin.mjs
304async function plugin (fastify, opts) {
305 fastify.get('/', async (req, reply) => {
306 return { hello: 'world' }
307 })
308}
309
310export default plugin
311```
312
313<a name="handle-errors"></a>
314## Handle errors
315It can happen that one of your plugins fails during startup. Maybe you expect it and you have a custom logic that will be triggered in that case. How can you implement this?
316The `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>
317The callback changes based on the parameters you are giving:
318
3191. If no parameter is given to the callback and there is an error, that error will be passed to the next error handler.
3201. If one parameter is given to the callback, that parameter will be the error object.
3211. If two parameters are given to the callback, the first will be the error object, the second will be the done callback.
3221. 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.
323
324Let's see how to use it:
325```js
326fastify
327 .register(require('./database-connector'))
328 .after(err => {
329 if (err) throw err
330 })
331```
332
333<a name="start"></a>
334## Let's start!
335Awesome, 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!
336
337If you want to see some real-world example, check out:
338- [`point-of-view`](https://github.com/fastify/point-of-view)
339Templates rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.
340- [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb)
341Fastify MongoDB connection plugin, with this you can share the same MongoDB connection pool in every part of your server.
342- [`fastify-multipart`](https://github.com/fastify/fastify-multipart)
343Multipart support for Fastify
344- [`fastify-helmet`](https://github.com/fastify/fastify-helmet) Important security headers for Fastify
345
346
347*Do you feel like something is missing here? Let us know! :)*