├── .gitignore ├── LICENSE ├── README.md ├── commands.md ├── docker-compose.yml ├── monolith ├── app.js ├── package.json ├── plugins │ └── basic-auth.js ├── services │ ├── get.js │ ├── post.js │ ├── status.js │ └── timeline.js └── test │ ├── helper.js │ ├── plugins │ └── basic-auth.test.js │ └── services │ ├── get.test.js │ ├── post.test.js │ ├── status.test.js │ └── timeline.test.js ├── slides ├── code │ ├── basic-auth.js │ ├── encapsulation-log-level.js │ ├── intro.js │ ├── query.js │ └── test.js ├── config.js ├── images │ ├── basic-auth-over-plain-http.jpg │ ├── clients.gif │ ├── companies.png │ ├── dag-decorate.png │ ├── dag-fp-encapsulate.png │ ├── dag.png │ ├── elastic-logo-light.png │ ├── fastify-background.png │ ├── fastify-white-landscape.png │ ├── hack.png │ ├── kibana-apm.png │ ├── nearform-logo.png │ └── plugin-real-world.png ├── package.json ├── slides.deckset.md └── slides.md ├── step0 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ └── root.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ └── root.test.js ├── step1 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ └── root.test.js ├── step10 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── get.js │ ├── me.js │ ├── post.js │ ├── root.js │ ├── status.js │ └── timeline.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── get.test.js │ ├── me.test.js │ ├── post.test.js │ ├── root.test.js │ └── status.test.js ├── step11 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── get.js │ ├── me.js │ ├── post.js │ ├── root.js │ ├── status.js │ └── timeline.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── get.test.js │ ├── me.test.js │ ├── post.test.js │ ├── root.test.js │ ├── status.test.js │ └── timeline.test.js ├── step12 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── get.js │ ├── me.js │ ├── post.js │ ├── root.js │ ├── status.js │ └── timeline.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── get.test.js │ ├── me.test.js │ ├── post.test.js │ ├── root.test.js │ ├── status.test.js │ └── timeline.test.js ├── step13 ├── docker-compose.yml ├── me │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ │ ├── README.md │ │ ├── basic-auth.js │ │ └── support.js │ ├── services │ │ ├── me.js │ │ └── status.js │ └── test │ │ ├── helper.js │ │ ├── plugins │ │ └── support.test.js │ │ └── services │ │ ├── me.test.js │ │ └── status.test.js ├── post │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ │ ├── README.md │ │ ├── basic-auth.js │ │ └── support.js │ ├── services │ │ ├── get.js │ │ ├── post.js │ │ └── status.js │ └── test │ │ ├── helper.js │ │ ├── plugins │ │ └── support.test.js │ │ └── services │ │ ├── get.test.js │ │ ├── post.test.js │ │ └── status.test.js └── timeline │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js │ ├── services │ ├── status.js │ └── timeline.js │ └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── status.test.js │ └── timeline.test.js ├── step14 ├── docker-compose.yml ├── gateway │ ├── .dockerignore │ ├── Dockerfile │ ├── index.js │ └── package.json ├── me │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ │ ├── README.md │ │ ├── basic-auth.js │ │ └── support.js │ ├── services │ │ ├── me.js │ │ └── status.js │ └── test │ │ ├── helper.js │ │ ├── plugins │ │ └── support.test.js │ │ └── services │ │ ├── me.test.js │ │ └── status.test.js ├── post │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ │ ├── README.md │ │ ├── basic-auth.js │ │ └── support.js │ ├── services │ │ ├── get.js │ │ ├── post.js │ │ └── status.js │ └── test │ │ ├── helper.js │ │ ├── plugins │ │ └── support.test.js │ │ └── services │ │ ├── get.test.js │ │ ├── post.test.js │ │ └── status.test.js └── timeline │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── package.json │ ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js │ ├── services │ ├── status.js │ └── timeline.js │ └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── status.test.js │ └── timeline.test.js ├── step2 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── root.test.js │ └── status.test.js ├── step3 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── root.test.js │ └── status.test.js ├── step4 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ ├── basic-auth.test.js │ └── support.test.js │ └── services │ ├── example.test.js │ ├── root.test.js │ └── status.test.js ├── step5 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── me.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── me.test.js │ ├── root.test.js │ └── status.test.js ├── step6 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── me.js │ ├── post.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── me.test.js │ ├── root.test.js │ └── status.test.js ├── step7 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── me.js │ ├── post.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── me.test.js │ ├── post.test.js │ ├── root.test.js │ └── status.test.js ├── step8 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins │ ├── README.md │ ├── basic-auth.js │ └── support.js ├── services │ ├── README.md │ ├── example │ │ └── index.js │ ├── get.js │ ├── me.js │ ├── post.js │ ├── root.js │ └── status.js └── test │ ├── helper.js │ ├── plugins │ └── support.test.js │ └── services │ ├── example.test.js │ ├── me.test.js │ ├── post.test.js │ ├── root.test.js │ └── status.test.js └── step9 ├── .gitignore ├── README.md ├── app.js ├── package.json ├── plugins ├── README.md ├── basic-auth.js └── support.js ├── services ├── README.md ├── example │ └── index.js ├── get.js ├── me.js ├── post.js ├── root.js └── status.js └── test ├── helper.js ├── plugins └── support.test.js └── services ├── example.test.js ├── get.test.js ├── me.test.js ├── post.test.js ├── root.test.js └── status.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastify-architecture-workshop -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | # Status 2 | 3 | ```sh 4 | curl http://localhost:3000/status 5 | ``` 6 | 7 | # Get 8 | 9 | ```sh 10 | curl http://localhost:3000/post/:id \ 11 | -H 'Authorization: Basic YXJ5YTpzdGFyaw==' 12 | ``` 13 | 14 | # Post 15 | 16 | ```sh 17 | curl http://localhost:3000/post \ 18 | -X POST \ 19 | -H 'Authorization: Basic YXJ5YTpzdGFyaw==' \ 20 | -H 'Content-Type: application/json' \ 21 | -d '{"text":"hello world"}' 22 | ``` 23 | 24 | # Timeline 25 | 26 | ```sh 27 | curl http://localhost:3000/timeline?from=0 \ 28 | -H 'Authorization: Basic YXJ5YTpzdGFyaw==' 29 | ``` 30 | -------------------------------------------------------------------------------- /monolith/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monolith", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Tomas Della Vedova", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2", 30 | "pino-elasticsearch": "^4.2.0" 31 | }, 32 | "devDependencies": { 33 | "standard": "^14.3.1", 34 | "tap": "^14.9.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /monolith/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /monolith/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /monolith/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | async function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | await app.ready() 25 | 26 | // tear down our app after we are done 27 | t.tearDown(app.close.bind(app)) 28 | 29 | return app 30 | } 31 | 32 | module.exports = { 33 | config, 34 | build 35 | } 36 | -------------------------------------------------------------------------------- /monolith/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /slides/code/basic-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const basicAuth = require('fastify-basic-auth') 5 | 6 | const users = { 7 | arya: 'stark', // Basic YXJ5YTpzdGFyaw== 8 | jon: 'snow' // Basic am9uOnNub3c= 9 | } 10 | 11 | async function basicAuthPlugin (fastify, opts) { 12 | fastify.register(basicAuth, { validate }) 13 | fastify.decorateRequest('user', null) 14 | 15 | async function validate (username, password, req, reply) { 16 | if (users[username] !== password) { 17 | throw new Error('Invalid username or password') 18 | } 19 | 20 | req.user = { 21 | name: username, 22 | topics: username === 'arya' 23 | ? ['sword', 'death', 'weapon'] 24 | : ['sword', 'night', 'know'] 25 | } 26 | } 27 | } 28 | 29 | module.exports = fp(basicAuthPlugin) 30 | -------------------------------------------------------------------------------- /slides/code/encapsulation-log-level.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify')() 2 | 3 | fastify.register(require('./api/v1'), { 4 | prefix: '/v1', 5 | logLevel: 'error' 6 | }) 7 | 8 | fastify.register(require('./api/v2'), { 9 | prefix: '/v2', 10 | logLevel: 'debug' 11 | }) 12 | -------------------------------------------------------------------------------- /slides/code/intro.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fastify = require('fastify')() 4 | 5 | fastify.get('/', async (req, reply) => { 6 | return { hello: 'world' } 7 | }) 8 | 9 | fastify.listen(3000, console.log) 10 | -------------------------------------------------------------------------------- /slides/code/query.js: -------------------------------------------------------------------------------- 1 | const filterTopics = req.user.topics.map(t => { 2 | return { 3 | filter: { 4 | term: { topics: t } 5 | }, 6 | weight: 5 7 | } 8 | }) 9 | 10 | const query = { 11 | query: { 12 | function_score: { 13 | query: { 14 | match_all: {} 15 | }, 16 | functions: [ 17 | { 18 | gauss: { 19 | time: { 20 | origin: 'now', 21 | scale: '4h', 22 | offset: '2h', 23 | decay: 0.5 24 | } 25 | } 26 | }, 27 | ...filterTopics 28 | ], 29 | boost_mode: 'multiply' 30 | } 31 | }, 32 | size: 10, 33 | from: req.query.from || 0 34 | } 35 | -------------------------------------------------------------------------------- /slides/code/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | 5 | // the helper is a piece of scaffold 6 | // generated by fastify-cli 7 | const { build } = require('../helper') 8 | 9 | test('200 response', async t => { 10 | const app = await build(t) 11 | const response = await app.inject({ 12 | method: 'GET', 13 | url: '/status' 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 18 | }) 19 | -------------------------------------------------------------------------------- /slides/images/basic-auth-over-plain-http.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/basic-auth-over-plain-http.jpg -------------------------------------------------------------------------------- /slides/images/clients.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/clients.gif -------------------------------------------------------------------------------- /slides/images/companies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/companies.png -------------------------------------------------------------------------------- /slides/images/dag-decorate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/dag-decorate.png -------------------------------------------------------------------------------- /slides/images/dag-fp-encapsulate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/dag-fp-encapsulate.png -------------------------------------------------------------------------------- /slides/images/dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/dag.png -------------------------------------------------------------------------------- /slides/images/elastic-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/elastic-logo-light.png -------------------------------------------------------------------------------- /slides/images/fastify-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/fastify-background.png -------------------------------------------------------------------------------- /slides/images/fastify-white-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/fastify-white-landscape.png -------------------------------------------------------------------------------- /slides/images/hack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/hack.png -------------------------------------------------------------------------------- /slides/images/kibana-apm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/kibana-apm.png -------------------------------------------------------------------------------- /slides/images/nearform-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/nearform-logo.png -------------------------------------------------------------------------------- /slides/images/plugin-real-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delvedor/fastify-architecture-workshop/bd7c35a6d55f5c2f0cef829b2d012bc586074ec2/slides/images/plugin-real-world.png -------------------------------------------------------------------------------- /slides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slides", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "mdx-deck slides.md", 8 | "pdf": "mdx-deck-export pdf slides.md" 9 | }, 10 | "keywords": [], 11 | "author": "Tomas Della Vedova", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@mdx-deck/export": "^2.5.1", 15 | "code-surfer": "^2.0.0-alpha.6", 16 | "mdx-deck": "^2.4.0", 17 | "raw-loader": "^3.1.0", 18 | "styled-components": "^4.4.1", 19 | "theme-ui": "^0.2.46" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /step0/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step0/README.md: -------------------------------------------------------------------------------- 1 | # Step 0 2 | 3 | This is just the output generated by https://github.com/fastify/fastify-cli. 4 | -------------------------------------------------------------------------------- /step0/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-plugin": "^1.6.0", 20 | "fastify-autoload": "^1.0.0", 21 | "fastify-cli": "^1.3.0" 22 | }, 23 | "devDependencies": { 24 | "tap": "^14.9.2" 25 | } 26 | } -------------------------------------------------------------------------------- /step0/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step0/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step0/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step0/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step0/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step0/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step0/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step0/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step0/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step1/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step1/README.md: -------------------------------------------------------------------------------- 1 | # Step 1 2 | 3 | This step adds a `GET /status => { status: 'ok' }` route. 4 | -------------------------------------------------------------------------------- /step1/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-autoload": "^1.0.0", 20 | "fastify-cli": "^1.3.0", 21 | "fastify-plugin": "^1.6.0", 22 | "fluent-schema": "^0.7.5" 23 | }, 24 | "devDependencies": { 25 | "tap": "^14.9.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /step1/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step1/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step1/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step1/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step1/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step1/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step1/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step1/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step1/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step1/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step10/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step10/README.md: -------------------------------------------------------------------------------- 1 | # Step 10 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step10/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step10/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step10/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step10/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step10/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step10/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step10/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step10/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step10/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step10/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step10/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step10/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step10/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step10/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step10/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step11/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step11/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step11/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step11/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step11/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step11/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step11/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step11/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step11/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step11/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step11/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step11/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step11/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step11/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step11/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step11/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step12/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step12/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step12/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monolith", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step12/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step12/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step12/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step12/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step12/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step12/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step12/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step12/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step12/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step12/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step12/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step12/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step12/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step12/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step13/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | post: 5 | build: ./post 6 | image: post-ms 7 | command: ["npm", "run", "apm"] 8 | ports: 9 | - "3030:3030" 10 | environment: 11 | - FASTIFY_PORT=3030 12 | volumes: 13 | - ./post/:/usr/src/app 14 | - /usr/src/app/node_modules 15 | 16 | me: 17 | build: ./me 18 | image: me-ms 19 | command: ["npm", "run", "apm"] 20 | ports: 21 | - "3031:3031" 22 | environment: 23 | - FASTIFY_PORT=3031 24 | volumes: 25 | - ./post/:/usr/src/app 26 | - /usr/src/app/node_modules 27 | 28 | timeline: 29 | build: ./timeline 30 | image: timeline-ms 31 | command: ["npm", "run", "apm"] 32 | ports: 33 | - "3032:3032" 34 | environment: 35 | - FASTIFY_PORT=3032 36 | volumes: 37 | - ./post/:/usr/src/app 38 | - /usr/src/app/node_modules 39 | -------------------------------------------------------------------------------- /step13/me/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step13/me/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3032 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step13/me/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step13/me/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "me", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step13/me/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step13/me/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step13/me/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step13/me/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step13/me/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step13/me/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step13/me/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step13/me/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step13/post/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step13/post/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3030 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step13/post/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step13/post/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "post", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step13/post/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step13/post/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step13/post/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step13/post/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step13/post/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step13/post/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step13/post/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step13/timeline/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step13/timeline/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3033 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step13/timeline/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step13/timeline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timeline", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step13/timeline/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step13/timeline/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step13/timeline/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step13/timeline/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step13/timeline/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step13/timeline/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step14/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | post: 5 | build: ./gateway 6 | image: post-gt 7 | command: ["npm", "run", "apm"] 8 | ports: 9 | - "3000:3000" 10 | environment: 11 | - FASTIFY_PORT=3000 12 | volumes: 13 | - ./post/:/usr/src/app 14 | - /usr/src/app/node_modules 15 | 16 | post: 17 | build: ./post 18 | image: post-gt 19 | command: ["npm", "run", "apm"] 20 | ports: 21 | - "3030:3030" 22 | environment: 23 | - FASTIFY_PORT=3030 24 | volumes: 25 | - ./post/:/usr/src/app 26 | - /usr/src/app/node_modules 27 | 28 | me: 29 | build: ./me 30 | image: me-gt 31 | command: ["npm", "run", "apm"] 32 | ports: 33 | - "3031:3031" 34 | environment: 35 | - FASTIFY_PORT=3031 36 | volumes: 37 | - ./post/:/usr/src/app 38 | - /usr/src/app/node_modules 39 | 40 | timeline: 41 | build: ./timeline 42 | image: timeline-gt 43 | command: ["npm", "run", "apm"] 44 | ports: 45 | - "3032:3032" 46 | environment: 47 | - FASTIFY_PORT=3032 48 | volumes: 49 | - ./post/:/usr/src/app 50 | - /usr/src/app/node_modules 51 | -------------------------------------------------------------------------------- /step14/gateway/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step14/gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3033 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step14/gateway/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fastify = require('fastify')({ logger: true }) 4 | const proxy = require('fastify-http-proxy') 5 | 6 | fastify.register(proxy, { 7 | upstream: 'http://localhost:3030', 8 | prefix: '/post' 9 | }) 10 | 11 | fastify.register(proxy, { 12 | upstream: 'http://localhost:3031', 13 | prefix: '/me' 14 | }) 15 | 16 | fastify.register(proxy, { 17 | upstream: 'http://localhost:3032', 18 | prefix: '/timeline' 19 | }) 20 | 21 | fastify.listen(3000) 22 | -------------------------------------------------------------------------------- /step14/gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gateway", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Tomas Della Vedova", 11 | "license": "ISC", 12 | "dependencies": { 13 | "elastic-apm-node": "^3.1.0", 14 | "fastify": "^2.10.0", 15 | "fastify-http-proxy": "^2.3.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /step14/me/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step14/me/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3032 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step14/me/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step14/me/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "me", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step14/me/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step14/me/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step14/me/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step14/me/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step14/me/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step14/me/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step14/me/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step14/me/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step14/post/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step14/post/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3030 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step14/post/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step14/post/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "post", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step14/post/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step14/post/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step14/post/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step14/post/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step14/post/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step14/post/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step14/post/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step14/timeline/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /step14/timeline/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | COPY package*.json . 8 | RUN npm install --production 9 | 10 | # Bundle app source 11 | COPY . . 12 | 13 | EXPOSE 3033 14 | CMD [ "npm", "run", "dev" ] 15 | -------------------------------------------------------------------------------- /step14/timeline/README.md: -------------------------------------------------------------------------------- 1 | # Step 11 2 | 3 | Create an endpoint that the return a user timeline 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3000/timeline 9 | ``` 10 | -------------------------------------------------------------------------------- /step14/timeline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timeline", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js", 13 | "apm": "NODE_OPTIONS=\"-r elastic-apm-node/start\" fastify start -l info app.js" 14 | }, 15 | "keywords": [], 16 | "author": "Matteo Collina ", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 20 | "elastic-apm-node": "^3.1.0", 21 | "env-schema": "^1.1.0", 22 | "fastify": "^2.10.0", 23 | "fastify-autoload": "^1.0.0", 24 | "fastify-basic-auth": "^0.4.0", 25 | "fastify-cli": "^1.3.0", 26 | "fastify-elasticsearch": "^1.0.0", 27 | "fastify-plugin": "^1.6.0", 28 | "fluent-schema": "^0.7.5", 29 | "hyperid": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "tap": "^14.9.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /step14/timeline/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step14/timeline/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step14/timeline/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step14/timeline/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step14/timeline/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step14/timeline/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step2/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step2/README.md: -------------------------------------------------------------------------------- 1 | # Step 2 2 | 3 | Add a test for the `/status` route. 4 | -------------------------------------------------------------------------------- /step2/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-autoload": "^1.0.0", 20 | "fastify-cli": "^1.3.0", 21 | "fastify-plugin": "^1.6.0", 22 | "fluent-schema": "^0.7.5" 23 | }, 24 | "devDependencies": { 25 | "tap": "^14.9.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /step2/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step2/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step2/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step2/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step2/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step2/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step2/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step2/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step2/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step2/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step2/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step3/README.md: -------------------------------------------------------------------------------- 1 | # Step 3 2 | 3 | Integrate [fastify-basic-auth](https://github.com/fastify/fastify-basic-auth) into a `plugins/basic-auth.js`. 4 | -------------------------------------------------------------------------------- /step3/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-autoload": "^1.0.0", 20 | "fastify-basic-auth": "^0.4.0", 21 | "fastify-cli": "^1.3.0", 22 | "fastify-plugin": "^1.6.0", 23 | "fluent-schema": "^0.7.5" 24 | }, 25 | "devDependencies": { 26 | "tap": "^14.9.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /step3/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step3/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step3/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step3/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step3/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step3/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step3/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step3/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step3/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step3/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step3/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step4/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step4/README.md: -------------------------------------------------------------------------------- 1 | # Step 4 2 | 3 | Add a test for `plugins/basic-auth.js`. 4 | -------------------------------------------------------------------------------- /step4/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-autoload": "^1.0.0", 20 | "fastify-basic-auth": "^0.4.0", 21 | "fastify-cli": "^1.3.0", 22 | "fastify-plugin": "^1.6.0", 23 | "fluent-schema": "^0.7.5" 24 | }, 25 | "devDependencies": { 26 | "tap": "^14.9.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /step4/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step4/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step4/services/README.md: -------------------------------------------------------------------------------- 1 | # Services Folder 2 | 3 | Services define routes within your application. Fastify provides an 4 | easy path to a microservice architecture, in the future you might want 5 | to independently deploy some of those. 6 | 7 | In this folder you should define all the services that define the routes 8 | of your web application. 9 | Each service is a [Fastify 10 | plugin](https://www.fastify.io/docs/latest/Plugins/), it is 11 | encapsulated (it can have its own independent plugins) and it is 12 | typically stored in a file; be careful to group your routes logically, 13 | e.g. all `/users` routes in a `users.js` file. We have added 14 | a `root.js` file for you with a '/' root added. 15 | 16 | If a single file become too large, create a folder and add a `index.js` file there: 17 | this file must be a Fastify plugin, and it will be loaded automatically 18 | by the application. You can now add as many files as you want inside that folder. 19 | In this way you can create complex services within a single monolith, 20 | and eventually extract them. 21 | 22 | If you need to share functionality between services, place that 23 | functionality into the `plugins` folder, and share it via 24 | [decorators](https://www.fastify.io/docs/latest/Decorators/). 25 | -------------------------------------------------------------------------------- /step4/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step4/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step4/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step4/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step4/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step4/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step4/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step4/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step5/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step5/README.md: -------------------------------------------------------------------------------- 1 | # Step 5 2 | 3 | Create a `GET /me` route that returns `req.user`. Add a test. 4 | -------------------------------------------------------------------------------- /step5/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const AutoLoad = require('fastify-autoload') 5 | 6 | module.exports = function (fastify, opts, next) { 7 | // Place here your custom code! 8 | 9 | // Do not touch the following lines 10 | 11 | // This loads all plugins defined in plugins 12 | // those should be support plugins that are reused 13 | // through your application 14 | fastify.register(AutoLoad, { 15 | dir: path.join(__dirname, 'plugins'), 16 | options: Object.assign({}, opts) 17 | }) 18 | 19 | // This loads all plugins defined in services 20 | // define your routes in one of these 21 | fastify.register(AutoLoad, { 22 | dir: path.join(__dirname, 'services'), 23 | options: Object.assign({}, opts) 24 | }) 25 | 26 | // Make sure to call next when done 27 | next() 28 | } 29 | -------------------------------------------------------------------------------- /step5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "fastify": "^2.10.0", 19 | "fastify-autoload": "^1.0.0", 20 | "fastify-basic-auth": "^0.4.0", 21 | "fastify-cli": "^1.3.0", 22 | "fastify-plugin": "^1.6.0", 23 | "fluent-schema": "^0.7.5" 24 | }, 25 | "devDependencies": { 26 | "tap": "^14.9.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /step5/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step5/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step5/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step5/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step5/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step5/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step5/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step5/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step5/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step5/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step5/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step5/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step6/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step6/README.md: -------------------------------------------------------------------------------- 1 | # Step 6 2 | 3 | Create an endpoint to index a new tweet. 4 | 5 | Test with: 6 | 7 | ``` 8 | curl -X POST -H "Authorization: Basic am9uOnNub3c=" -H "Content-Type: application/json" http://localhost:3000/post -d '{"text":"hello"}' 9 | ``` 10 | -------------------------------------------------------------------------------- /step6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step6/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step6/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step6/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step6/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step6/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step6/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step6/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step6/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step6/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step6/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step6/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step6/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step7/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step7/README.md: -------------------------------------------------------------------------------- 1 | # Step 6 2 | 3 | Create a test for the `/post` route. 4 | 5 | -------------------------------------------------------------------------------- /step7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step7/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step7/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step7/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step7/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step7/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step7/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step7/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step7/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step7/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step7/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step7/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step7/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step8/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step8/README.md: -------------------------------------------------------------------------------- 1 | # Step 8 2 | 3 | Create an endpoint to get a new tweet. 4 | 5 | Create a tweet with: 6 | 7 | ``` 8 | curl -X POST -H "Authorization: Basic am9uOnNub3c=" -H "Content-Type: application/json" http://localhost:3000/post -d '{"text":"hello"}' 9 | ``` 10 | 11 | Use that id in the next curl: 12 | 13 | ``` 14 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3030/post/$TWEETID 15 | ``` 16 | -------------------------------------------------------------------------------- /step8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step8/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step8/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step8/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step8/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step8/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step8/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step8/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step8/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step8/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step8/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step8/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step8/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step8/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | -------------------------------------------------------------------------------- /step9/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # 0x 40 | profile-* 41 | 42 | # mac files 43 | .DS_Store 44 | 45 | # vim swap files 46 | *.swp 47 | 48 | # webstorm 49 | .idea 50 | 51 | # vscode 52 | .vscode 53 | *code-workspace 54 | 55 | # clinic 56 | profile* 57 | *clinic* 58 | *flamegraph* 59 | 60 | # lock files 61 | yarn.lock 62 | package-lock.json 63 | 64 | # generated code 65 | examples/typescript-server.js 66 | test/types/index.js 67 | -------------------------------------------------------------------------------- /step9/README.md: -------------------------------------------------------------------------------- 1 | # Step 8 2 | 3 | Create an endpoint to get a new tweet. 4 | 5 | Create a tweet with: 6 | 7 | ``` 8 | curl -X POST -H "Authorization: Basic am9uOnNub3c=" -H "Content-Type: application/json" http://localhost:3000/post -d '{"text":"hello"}' 9 | ``` 10 | 11 | Use that id in the next curl: 12 | 13 | ``` 14 | curl -H "Authorization: Basic am9uOnNub3c=" http://localhost:3030/post/$TWEETID 15 | ``` 16 | -------------------------------------------------------------------------------- /step9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tap test/**/*.test.js", 11 | "start": "fastify start -l info app.js", 12 | "dev": "fastify start -l info -P app.js" 13 | }, 14 | "keywords": [], 15 | "author": "Matteo Collina ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@delvedor/fastify-workshop-dataset": "^1.0.1", 19 | "env-schema": "^1.1.0", 20 | "fastify": "^2.10.0", 21 | "fastify-autoload": "^1.0.0", 22 | "fastify-basic-auth": "^0.4.0", 23 | "fastify-cli": "^1.3.0", 24 | "fastify-elasticsearch": "^1.0.0", 25 | "fastify-plugin": "^1.6.0", 26 | "fluent-schema": "^0.7.5", 27 | "hyperid": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "tap": "^14.9.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /step9/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Plugins Folder 2 | 3 | Plugins define behavior that is common to all the routes in your 4 | application. Authentication, caching, templates, and all the other cross 5 | cutting concerns should be handled by plugins placed in this folder. 6 | 7 | Files in this folder are typically defined through the 8 | [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module, 9 | making them non-encapsulated. They can define decorators and set hooks 10 | that will then be used in the rest of your application. 11 | 12 | Check out: 13 | 14 | * [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md) 15 | * [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/). 16 | * [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/). 17 | -------------------------------------------------------------------------------- /step9/plugins/support.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | // the use of fastify-plugin is required to be able 6 | // to export the decorators to the outer scope 7 | 8 | module.exports = fp(function (fastify, opts, next) { 9 | fastify.decorate('someSupport', function () { 10 | return 'hugs' 11 | }) 12 | next() 13 | }) 14 | 15 | // If you prefer async/await, use the following 16 | // 17 | // module.exports = fp(async function (fastify, opts) { 18 | // fastify.decorate('someSupport', function () { 19 | // return 'hugs' 20 | // }) 21 | // }) 22 | -------------------------------------------------------------------------------- /step9/services/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/example', function (request, reply) { 5 | reply.send('this is an example') 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/example', async function (request, reply) { 15 | // return 'this is an example' 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step9/services/get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function getService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/post/:id', 9 | onRequest: fastify.basicAuth, 10 | handler: onGetPost, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('id', S.string()) 15 | .prop('text', S.string()) 16 | .prop('time', S.string()) 17 | .prop('user', S.string()) 18 | .prop('topics', S.array().items(S.string())) 19 | } 20 | } 21 | }) 22 | 23 | async function onGetPost (req, reply) { 24 | const { body, statusCode } = await this.elastic.get({ 25 | index: 'tweets', 26 | id: req.params.id 27 | }, { 28 | ignore: [404] 29 | }) 30 | 31 | if (statusCode === 404) { 32 | reply.code(404) 33 | return new Error('Not Found') 34 | } 35 | 36 | return body._source 37 | } 38 | } 39 | 40 | module.exports = getService 41 | -------------------------------------------------------------------------------- /step9/services/me.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function meService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/me', 9 | handler: onMe, 10 | onRequest: fastify.basicAuth, 11 | schema: { 12 | response: { 13 | 200: S.object() 14 | .prop('name', S.string()) 15 | .prop('topics', S.array().items(S.string())) 16 | } 17 | } 18 | }) 19 | 20 | async function onMe (req, reply) { 21 | return req.user 22 | } 23 | } 24 | 25 | module.exports = meService 26 | -------------------------------------------------------------------------------- /step9/services/root.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (fastify, opts, next) { 4 | fastify.get('/', function (request, reply) { 5 | reply.send({ root: true }) 6 | }) 7 | 8 | next() 9 | } 10 | 11 | // If you prefer async/await, use the following 12 | // 13 | // module.exports = async function (fastify, opts) { 14 | // fastify.get('/', async function (request, reply) { 15 | // return { root: true } 16 | // }) 17 | // } 18 | -------------------------------------------------------------------------------- /step9/services/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | async function statusService (fastify, opts) { 6 | fastify.route({ 7 | method: 'GET', 8 | path: '/status', 9 | handler: onStatus, 10 | schema: { 11 | response: { 12 | 200: S.object().prop('status', S.string()) 13 | } 14 | } 15 | }) 16 | 17 | async function onStatus (req, reply) { 18 | return { status: 'ok' } 19 | } 20 | } 21 | 22 | module.exports = statusService 23 | -------------------------------------------------------------------------------- /step9/test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This file contains code that we reuse 4 | // between our tests. 5 | 6 | const Fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const App = require('../app') 9 | 10 | // Fill in this config with all the configurations 11 | // needed for testing the application 12 | function config () { 13 | return {} 14 | } 15 | 16 | // automatically build and tear down our instance 17 | function build (t) { 18 | const app = Fastify() 19 | 20 | // fastify-plugin ensures that all decorators 21 | // are exposed for testing purposes, this is 22 | // different from the production setup 23 | app.register(fp(App), config()) 24 | 25 | // tear down our app after we are done 26 | t.tearDown(app.close.bind(app)) 27 | 28 | return app 29 | } 30 | 31 | module.exports = { 32 | config, 33 | build 34 | } 35 | -------------------------------------------------------------------------------- /step9/test/plugins/support.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Fastify = require('fastify') 5 | const Support = require('../../plugins/support') 6 | 7 | test('support works standalone', (t) => { 8 | t.plan(2) 9 | const fastify = Fastify() 10 | fastify.register(Support) 11 | 12 | fastify.ready((err) => { 13 | t.error(err) 14 | t.equal(fastify.someSupport(), 'hugs') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('support works standalone', async (t) => { 21 | // const fastify = Fastify() 22 | // fastify.register(Support) 23 | // 24 | // await fastify.ready() 25 | // t.equal(fastify.someSupport(), 'hugs') 26 | // }) 27 | -------------------------------------------------------------------------------- /step9/test/services/example.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('example is loaded', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/example' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.equal(res.payload, 'this is an example') 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('example is loaded', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/example' 25 | // }) 26 | // t.equal(res.payload, 'this is an example') 27 | // }) 28 | -------------------------------------------------------------------------------- /step9/test/services/me.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/me', 11 | headers: { 12 | Authorization: 'Basic YXJ5YTpzdGFyaw==' 13 | } 14 | }) 15 | 16 | t.strictEqual(response.statusCode, 200) 17 | t.deepEqual(JSON.parse(response.payload), { 18 | name: 'arya', 19 | topics: [ 20 | 'sword', 21 | 'death', 22 | 'weapon' 23 | ] 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /step9/test/services/root.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('default root route', (t) => { 7 | t.plan(2) 8 | const app = build(t) 9 | 10 | app.inject({ 11 | url: '/' 12 | }, (err, res) => { 13 | t.error(err) 14 | t.deepEqual(JSON.parse(res.payload), { root: true }) 15 | }) 16 | }) 17 | 18 | // If you prefer async/await, use the following 19 | // 20 | // test('default root route', async (t) => { 21 | // const app = build(t) 22 | // 23 | // const res = await app.inject({ 24 | // url: '/' 25 | // }) 26 | // t.deepEqual(JSON.parse(res.payload), { root: true }) 27 | // }) 28 | -------------------------------------------------------------------------------- /step9/test/services/status.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { build } = require('../helper') 5 | 6 | test('200 response', async t => { 7 | const app = await build(t) 8 | const response = await app.inject({ 9 | method: 'GET', 10 | url: '/status' 11 | }) 12 | 13 | t.strictEqual(response.statusCode, 200) 14 | t.deepEqual(JSON.parse(response.payload), { status: 'ok' }) 15 | }) 16 | --------------------------------------------------------------------------------