-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve getting started #462
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,18 @@ | ||
<h1 align="center">Fastify</h1> | ||
|
||
## Getting Started | ||
Hello! Thank you for checking out Fastify! | ||
This document aims to be a gentle introduction to the framework and its features. It is an elementary introduction with examples and links to other parts of the documentation. | ||
Let's start! | ||
|
||
<a name="install"></a> | ||
### Install | ||
``` | ||
npm i fastify --save | ||
``` | ||
<a name="first-server"></a> | ||
### Your first server | ||
Let's write a *Hello world* server! | ||
Let's write our first server: | ||
```js | ||
// Require the framework and instantiate it | ||
const fastify = require('fastify')() | ||
|
@@ -20,97 +25,186 @@ fastify.get('/', function (request, reply) { | |
// Run the server! | ||
fastify.listen(3000, function (err) { | ||
if (err) throw err | ||
console.log(`server listening on ${fastify.server.address().port}`) | ||
fastify.log.info(`server listening on ${fastify.server.address().port}`) | ||
}) | ||
``` | ||
|
||
Do you prefer to use `async/await`? Please review [Routes#async-await](https://github.com/fastify/fastify/blob/master/docs/Routes.md#async-await)! | ||
|
||
<a name="schema"></a> | ||
### Schema serialization | ||
Fastify is designed for performance. To truly turbocharge our server, we can serialize responses according to the [JSON Schema](http://json-schema.org/) standard: | ||
Do you prefer to use `async/await`? Fastify supports it out-of-the-box. | ||
*(we also suggest using [make-promises-safe](https://github.com/mcollina/make-promises-safe) to avoid file descriptor and memory leaks)* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is out of the context. Maybe a recommendation is better for this tips |
||
```js | ||
const fastify = require('fastify')() | ||
|
||
const opts = { | ||
schema: { | ||
response: { | ||
200: { | ||
type: 'object', | ||
properties: { | ||
hello: { type: 'string' } | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Declare a route with an output schema | ||
fastify.get('/', opts, function (request, reply) { | ||
reply.send({ hello: 'world' }) | ||
fastify.get('/', async (request, reply) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if you need to set some custom header o change the status code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then maybe a note about it and why it isn't being used here would be good to have. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that a note could be a nice idea, but I think that this document should provide just a getting started, we have provided the link to other parts of the documentation that explains better the usage of every api. I don't think that add here too many info could be a good idea. |
||
return { hello: 'world' } | ||
}) | ||
|
||
fastify.listen(3000, function (err) { | ||
if (err) throw err | ||
console.log(`server listening on ${fastify.server.address().port}`) | ||
fastify.log.info(`server listening on ${fastify.server.address().port}`) | ||
}) | ||
``` | ||
|
||
*To learn more about using serialization and validation, see [Validation and Serialization](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md)!* | ||
Awesome, that was easy. | ||
Unfortunately, writing a complex application requires significantly more code than this example. A classic problem when you are building a new application is how handle multiple files, asynchronous bootstrapping and the architecture of your code. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Fastify offers an easy platform that helps solve all of problems, and more. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or add a "these" between "of" and "problems". |
||
|
||
<a name="register"></a> | ||
### Register | ||
As you can see, using Fastify is very easy and handy. Obviously, registering all of your routes in the same file is not a good idea, so Fastify offers a utility to solve this problem, `register`: | ||
<a name="first-plugin"></a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. => |
||
### Your first plugin | ||
As with JavaScript everything is an object, with Fastify everything is a plugin. | ||
Before digging into it, let's see how it works! | ||
Let's declare our basic server, but instead of declaring the route inside the entry point, we'll declare it in an external file (checkout the [route declaration](https://github.com/fastify/fastify/blob/master/docs/Routes.md) docs). | ||
```js | ||
/* server.js */ | ||
|
||
const fastify = require('fastify')() | ||
|
||
fastify.register(require('./route')) | ||
fastify.register(require('./our-first-route')) | ||
|
||
const opts = { | ||
hello: 'world', | ||
something: true | ||
fastify.listen(3000, function (err) { | ||
if (err) throw err | ||
fastify.log.info(`server listening on ${fastify.server.address().port}`) | ||
}) | ||
``` | ||
|
||
```js | ||
// our-first-route.js | ||
|
||
async function routes (fastify, options) { | ||
fastify.get('/', async (request, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
} | ||
|
||
fastify.register([ | ||
require('./another-route'), | ||
require('./yet-another-route') | ||
], opts, function (err) { | ||
if (err) throw err | ||
module.exports = routes | ||
``` | ||
In this example we used the `register` API. This API is the core of the Fastify framework, and is the only way to register routes, plugins and so on. | ||
|
||
At the beginning of this guide we noted that Fastify provides a foundation that assists with the asynchronous bootstrapping of your an application. Why this is important? | ||
Consider the scenario where a database connection is needed to handle data storage. Obviously the database connection needs to be available prior to the server accepting connections. How do we address this problem? | ||
A typical solution is to use a complex callback, or promises, system that will mix the framework API with other libraries and the application code. | ||
Fastify handles this internally, with minimum effort! | ||
|
||
Let's rewrite the above example with a database connection. | ||
*(we will use a simple example, for a robust solution consider using [`fastify-mongo`](https://github.com/fastify/fastify-mongodb) or another in the Fastify [ecosystem](https://github.com/fastify/fastify/blob/master/docs/Ecosystem.md))* | ||
```js | ||
const fastify = require('fastify')() | ||
|
||
fastify.register(require('./our-db-connector'), { | ||
url: 'mongodb://mongo/db' | ||
}) | ||
fastify.register(require('./our-first-route')) | ||
|
||
fastify.listen(8000, function (err) { | ||
fastify.listen(3000, function (err) { | ||
if (err) throw err | ||
console.log(`server listening on ${fastify.server.address().port}`) | ||
fastify.log.info(`server listening on ${fastify.server.address().port}`) | ||
}) | ||
``` | ||
|
||
```js | ||
/* route.js */ | ||
// our-db-connector.js | ||
const MongoClient = require('mongodb').MongoClient | ||
|
||
module.exports = function (fastify, options, next) { | ||
fastify.get('/', function (req, reply) { | ||
reply.send({ hello: 'world' }) | ||
}) | ||
next() | ||
async function db (fastify, options) { | ||
const url = options.url | ||
delete options.url | ||
|
||
const db = await MongoClient.connect(url, options) | ||
fastify.decorate('mongo', db) | ||
} | ||
|
||
module.exports = db | ||
``` | ||
|
||
or with `async/await`: | ||
```js | ||
/* route.js */ | ||
// our-first-route.js | ||
|
||
async function routes (fastify, options) { | ||
const collection = fastify.mongo.collection('test') | ||
|
||
module.exports = async function (fastify, options) { | ||
fastify.get('/', function (req, reply) { | ||
reply.send({ hello: 'world' }) | ||
fastify.get('/', async (request, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
|
||
fastify.get('/search/:id', async (request, reply) => { | ||
return await collection.findOne({ id: req.params.id }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the query doesn't return any document, which is the behaviour? I know that this is an example but maybe:
It easies to introduce the build-in error handing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a very basic example, I don't want to make it more complex than needed. Here we are just showing the async/await support and how use a db connector with decorate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a big fan of "incomplete" examples. Anyway I understand this is out of the scope of "Your first plugin" section. But I'd like to have a section in this file that describes the error handling: it is a great feature! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Me too, but your proposal is out of scope. If you show the usage of Boom in a getting started example a user will probably think that is required, which is completely false. The error handling is described in the plugin guide. |
||
}) | ||
} | ||
|
||
module.exports = routes | ||
``` | ||
|
||
Wow, that was fast! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. |
||
Let's recap what we have done here since we've introduced some new concepts. | ||
As you can see, we used `register` both for the database connector and the routes registration. | ||
This is one of the best features of Fastify, it will load your plugins in the same order you declare them, and it will load the next plugin only once the current one has been loaded. In this way we can register the database connector in the first plugin and use it in the second. | ||
|
||
We have used the `decorate` api API. Let's take a moment to understand what it is and how it works. A scenario is to use the same code/library in different parts of an application. A solution is to require the code/library that it is needed. it This works, but is annoying because of duplicated code repeated and, if needed, long refactors. | ||
To solve this Fastify offers the `decorate` API, which adds custom objects to the Fastify namespace, so that they can be used everywhere. | ||
|
||
To dig deeper into how Fastify plugins work, how to develop new plugins, and for details on how to use the whole Fastify API to deal with the complexity of asynchronously bootstrapping an application, read [the hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins.md). | ||
|
||
<a name="validate-data"></a> | ||
### Validate your data | ||
Data validation is extremely important and is a core concept of the framework. | ||
To validate incoming requests, Fastify uses [JSON Schema](http://json-schema.org/). | ||
Let's look at an example demonstrating validation for routes: | ||
```js | ||
const opts = { | ||
schema: { | ||
body: { | ||
type: 'object', | ||
properties: { | ||
someKey: { type: 'string' }, | ||
someOtherKey: { type: 'number' } | ||
} | ||
} | ||
} | ||
} | ||
|
||
fastify.post('/', opts, async (request, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
``` | ||
This example shows how to pass an options object to the route, which accepts a `schema` key, that contains all of the schemas for route, `body`, `querystring`, `params` and `headers`. | ||
Read [Validation and Serialization](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md) to learn more. | ||
|
||
<a name="serialize-data"></a> | ||
### Serialize your data | ||
Fastify has first class support for JSON. It is extremely optimized to parse a JSON body and to serialize JSON output. | ||
To speed up JSON serialization (yes, it is slow!) use the `response` key of the schema option like so: | ||
```js | ||
const opts = { | ||
schema: { | ||
response: { | ||
200: { | ||
type: 'object', | ||
properties: { | ||
hello: { type: 'string' } | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fastify.get('/', opts, async (request, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
``` | ||
Simply by specifying a schema as shown, a speed up your of serialization by 2x or even 3x can be achieved. This also helps protect against leaking of sensitive data, since Fastify will serialize only the data present in the response schema. | ||
Read [Validation and Serialization](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md) to learn more. | ||
|
||
<a name="extend-server"></a> | ||
### Extend your server | ||
Fastify is built to be extremely extensible and very minimal, We believe that a bare minimum framework is all that is necessary to make great applications possible. | ||
In other words, Fastify is not a "batteries included" framework, and relies on an amazing [ecosystem](https://github.com/fastify/fastify/blob/master/docs/Ecosystem.md)! | ||
|
||
<a name="test-server"></a> | ||
### Test your server | ||
Fastify does not offer a testing framework, but we do recommend a way to write your tests that uses the features and the architecture of Fastify. | ||
Read the [testing](https://github.com/fastify/fastify/blob/master/docs/Testing.md) documentation to learn more! | ||
|
||
<a name="cli"></a> | ||
### Run from CLI | ||
You can also run Fastify from the CLI thanks to [fastify-cli](https://github.com/fastify/fastify-cli). It's very easy! Simply add the following lines to your `package.json`: | ||
### Run your server from CLI | ||
Fastify also has CLI integration thanks to [fastify-cli](https://github.com/fastify/fastify-cli). It's very easy! | ||
Simply add the following lines to `package.json`: | ||
```json | ||
{ | ||
"scripts": { | ||
|
@@ -124,11 +218,10 @@ And create your server file(s): | |
// server.js | ||
'use strict' | ||
|
||
module.exports = function (fastify, opts, next) { | ||
fastify.get('/', (req, reply) => { | ||
reply.send({ hello: 'world' }) | ||
module.exports = async function (fastify, opts) { | ||
fastify.get('/', async (req, reply) => { | ||
return { hello: 'world' } | ||
}) | ||
next() | ||
} | ||
``` | ||
|
||
|
@@ -145,7 +238,4 @@ npm start | |
|
||
- Videos | ||
- [Take your HTTP server to ludicrous speed](https://www.youtube.com/watch?v=5z46jJZNe8k) by [@mcollina](https://github.com/mcollina) | ||
|
||
<a name="next"></a> | ||
### Next | ||
Do you want to learn more? Check out the documentation folder located [here](https://github.com/fastify/fastify/blob/master/docs/)! | ||
- [What if I told you that HTTP can be fast](https://www.webexpo.net/prague2017/talk/what-if-i-told-you-that-http-can-be-fast/) by [@delvedor](https://github.com/delvedor) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,7 @@ Your routes, your utilities and so on are all plugins. And to add a new plugin, | |
```js | ||
fastify.register( | ||
require('./my-plugin'), | ||
{ options }, | ||
callback | ||
{ options } | ||
) | ||
``` | ||
`register` creates for you a new Fastify context, this means that if you do any change to the Fastify instance, that change(s) will not be reflected into the context's ancestors. In other words, encapsulation! | ||
|
@@ -271,6 +270,26 @@ module.exports = fp(dbPlugin) | |
``` | ||
You can also tell to `fastify-plugin` to check the installed version of Fastify, in case of you need a specific api. | ||
|
||
<a name="handle-errors"></a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. => |
||
## Handle errors | ||
It 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? | ||
The `after` api is what you need. `after` simply register a callback that will be executed just after a register, and it can take up to three parameters. | ||
The callback changes basing on the parameters your are giving: | ||
|
||
1. If no parameter is given to the callback and there is an error, that error will be passed to the next error handler. | ||
1. If one parameter is given to the callback, that parameter will be the error object. | ||
1. If two parameters are given to the callback, the first will be the error object, the second will be the done callback. | ||
1. 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. | ||
|
||
Let's see how use it: | ||
```js | ||
fastify | ||
.register(require('./database-connector')) | ||
.after(err => { | ||
if (err) throw err | ||
}) | ||
``` | ||
|
||
<a name="start"></a> | ||
## Let's start! | ||
Awesome, 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! | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
<a id="first-server"></a>