Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Middy.js - A powerful Node.js middleware framework for your lambdas​

Middy.js - A powerful Node.js middleware framework for your lambdas​

An introduction talk to Middy, The stylish Node.js middleware engine for AWS Lambda!

Luciano Mammino

January 29, 2020
Tweet

More Decks by Luciano Mammino

Other Decks in Technology

Transcript

  1. Middy.js A powerful Node.js middleware framework for your lambdas Luciano

    Mammino ( ) @loige loige.link/middy-gcs 29/01/2020, Dublin 1
  2. 2

  3. Hello, I am Luciano! Hello, I am Luciano! Fullstack Dev

    & Cloud Architect Blog: Twitter: GitHub: loige.co @loige @lmammino 2
  4. Hello, I am Luciano! Hello, I am Luciano! Fullstack Dev

    & Cloud Architect Blog: Twitter: GitHub: loige.co @loige @lmammino 2
  5. What is serverless What is serverless Compute as functions (FaaS)

    Compute as functions (FaaS) Event-based model Event-based model Managed resources Managed resources @loige loige.link/middy-gcs-qa 4
  6. Why serverless is good Why serverless is good Focus on

    business logic Focus on business logic Scalability Scalability Pay-per-use model (compute time * memory) Pay-per-use model (compute time * memory) Forces you to think micro-services Forces you to think micro-services @loige loige.link/middy-gcs-qa 5
  7. My Serverless experience My Serverless experience Open source A semi-automated

    weekly newsletter ( ) A middleware framework for AWS Lambda ( ) Enterprise Trading, Billing engine, Market data aggregator solutions ( ) Big data pipeline to make network metadata searchable ( ) Organised various workshops around Europe ( ) Fullstack bulletin middy.js.org ESB Vectra.ai Serverlesslab.com @loige loige.link/middy-gcs-qa 6
  8. ♀ ♀ DISCLAIMER DISCLAIMER Serverless is more than just FaaS

    Serverless is more than just FaaS ... but here we will focus mostly on AWS Lamda ... but here we will focus mostly on AWS Lamda @loige loige.link/middy-gcs-qa 7
  9. Quickest intro to Lambda (ever)! Quickest intro to Lambda (ever)!

    @loige loige.link/middy-gcs-qa code ⚡ event 8
  10. Quickest intro to Lambda (ever)! Quickest intro to Lambda (ever)!

    @loige loige.link/middy-gcs-qa code ⚡ event 8
  11. Quickest intro to Lambda (ever)! Quickest intro to Lambda (ever)!

    @loige loige.link/middy-gcs-qa code ⚡ event = 8
  12. Quickest intro to Lambda (ever)! Quickest intro to Lambda (ever)!

    @loige loige.link/middy-gcs-qa code ⚡ event profit = 8
  13. Quickest intro to Lambda (ever)! Quickest intro to Lambda (ever)!

    @loige loige.link/middy-gcs-qa code ⚡ event profit = * real life coding with Lambda is a little bit more complicated™ than this... 8
  14. exports.myLambda = async function ( event, context ) { //

    get input from event and context // return output or throw an error } Anatomy of a Node.js lambda on AWS Anatomy of a Node.js lambda on AWS @loige loige.link/middy-gcs-qa 9
  15. exports.myLambda = async function (event, context) { const name =

    event.queryStringParameters.name const message = `Hello ${name || 'World'}!` return ({ statusCode: 200, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message }) }) } Hello World! Hello World! @loige loige.link/middy-gcs-qa 10
  16. exports.MyLambda = async function (event, context) { // decrypt environment

    variables with KMS // deserialize the content of the event // validate input, authentication, authorization // REAL BUSINESS LOGIC // (process input, generate output) // validate output // serialize response // handle errors } Real World Real World vs vs Hello World Hello World @loige loige.link/middy-gcs-qa 11
  17. exports.MyLambda = async function (event, context) { // decrypt environment

    variables with KMS // deserialize the content of the event // validate input, authentication, authorization // REAL BUSINESS LOGIC // (process input, generate output) // validate output // serialize response // handle errors } Real World Real World vs vs Hello World Hello World @loige loige.link/middy-gcs-qa 11
  18. exports.MyLambda = async function (event, context) { // decrypt environment

    variables with KMS // deserialize the content of the event // validate input, authentication, authorization // REAL BUSINESS LOGIC // (process input, generate output) // validate output // serialize response // handle errors } Real World Real World vs vs Hello World Hello World @loige loige.link/middy-gcs-qa 11
  19. exports.MyLambda = async function (event, context) { // decrypt environment

    variables with KMS // deserialize the content of the event // validate input, authentication, authorization // REAL BUSINESS LOGIC // (process input, generate output) // validate output // serialize response // handle errors } Real World Real World vs vs Hello World Hello World LOTS of BOILERPLATE @loige loige.link/middy-gcs-qa 11
  20. ♀ ♀ In a real project with many lambdas, In

    a real project with many lambdas, we might end up with a lot of code duplication! we might end up with a lot of code duplication! @loige loige.link/middy-gcs-qa 12
  21. Can we avoid a bit of clutter by separating "

    Can we avoid a bit of clutter by separating "pure pure business logic business logic" from the " " from the "boilerplate boilerplate"? "? @loige loige.link/middy-gcs-qa 13
  22. npm install @middy/core Note: using (beta) version 1.0.0 Give it

    moar love @loige loige.link/middy-gcs-qa 15
  23. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } @loige loige.link/middy-gcs-qa 16
  24. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } 1. define handler @loige loige.link/middy-gcs-qa 16
  25. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } 2. "middify" the handler @loige loige.link/middy-gcs-qa 16
  26. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } 3. attach middlewares @loige loige.link/middy-gcs-qa 16
  27. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } 4. export "middyfied" handler @loige loige.link/middy-gcs-qa 16
  28. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } @loige loige.link/middy-gcs-qa 17
  29. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } Business logic @loige loige.link/middy-gcs-qa 17
  30. Usage Usage const middy = require('@middy/core') const { middleware1, middleware2,

    middleware3 } = require('some-middlewares') const originalHandler = async (event, context) => { /* your business logic */ } const handler = middy(originalHandler) handler .use(middleware1()) .use(middleware2()) .use(middleware3()) module.exports = { handler } Business logic Boilerplate @loige loige.link/middy-gcs-qa 17
  31. Middy comes with a built-in collection Middy comes with a

    built-in collection of useful middlewares of useful middlewares Some examples: Cache Database manager HTTP error handler HTTP content negotiation HTTP CORS HTTP JSON body parser HTTP Multipart body parser HTTP Security headers Secrets manager SSM Validator Warmup @loige loige.link/middy-gcs-qa 18
  32. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } 19
  33. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } Dependencies 19
  34. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } Handler (business logic) 19
  35. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } Define the input schema (uses ) JSON-schema 19
  36. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } Attach middlewares 19
  37. const middy = require('@middy/core') const urlEncodedBodyParser = require('@middy/http-urlencode-body-parser') const validator

    = require('@middy/validator') const httpErrorHandler = require('@middy/http-error-handler') const processPaymentHandler = async (event, context) => { const { creditCardNumber, expiryMonth, expiryYear, cvc, nameOnCard, amount } = event.body // do stuff with this data ... return ({ statusCode: 200, body: 'payment processed correctly' }) } const inputSchema = { // define validation schema here ... } const handler = middy(processPaymentHandler) .use(urlEncodedBodyParser()) .use(validator(inputSchema)) .use(httpErrorHandler()) module.exports = { handler } Export enhanced handler 19
  38. Why? Why? Simplify code Reusability input parsing input & output

    validation output serialization error handling... @loige loige.link/middy-gcs-qa 20
  39. Why? Why? Simplify code Reusability input parsing input & output

    validation output serialization error handling... Focus (even) MORE on business logic Testability @loige loige.link/middy-gcs-qa 20
  40. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) @loige 22 loige.link/middy-gcs-qa
  41. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) 4. handler @loige 22 loige.link/middy-gcs-qa
  42. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) 4. handler 5. middleware3 (after) @loige 22 loige.link/middy-gcs-qa
  43. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) 4. handler 5. middleware3 (after) 6. middleware2 (after) @loige 22 loige.link/middy-gcs-qa
  44. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) 4. handler 5. middleware3 (after) 6. middleware2 (after) 7. middleware1 (after) @loige 22 loige.link/middy-gcs-qa
  45. Execution order Execution order 1. middleware1 (before) 2. middleware2 (before)

    3. middleware3 (before) 4. handler 5. middleware3 (after) 6. middleware2 (after) 7. middleware1 (after) @loige 22 loige.link/middy-gcs-qa
  46. When an error happens... When an error happens... Flow is

    stopped First middleware implementing `onError` gets control It can choose to handle the error, or delegate it to the next handler If the error is handler a response is returned If the error is not handled the execution fails reporting the unhandled error @loige loige.link/middy-gcs-qa 23
  47. Writing a middleware Writing a middleware const myMiddleware = (config)

    => { // might set default options in config return ({ before: (handler, next) => { // might read options from `config` }, after: (handler, next) => { // might read options from `config` }, onError: (handler, next) => { // might read options from `config` } }) } module.exports = myMiddleware @loige loige.link/middy-gcs-qa 24
  48. Inline middlewares Inline middlewares const middy = require('@middy/core') const handler

    = middy((event, context, callback) => { // do stuff }) handler.before((handler, next) => { // do something in the before phase next() }) handler.after((handler, next) => { // do something in the after phase next() }) handler.onError((handler, next) => { // do something in the on error phase next() }) module.exports = { handler } @loige @loige loige.link/middy-gcs-qa 25
  49. In summary In summary Serverless is cool, it helps you

    to build apps quickly and with a greater focus on business logic, rather than on infrastructure! Middy helps you to keep focusing on your business logic first You can add extra behaviours with very minimal changes to your core logic by introducing dedicated middlewares You can easily share common functionality through middlewares @loige loige.link/middy-gcs-qa 26
  50. If you like Middy If you like Middy Use it

    Use it Give feedback: Give feedback: Contribute (I am looking for co-maintainers) Contribute (I am looking for co-maintainers) Version 1.0 (stable) coming soon Version 1.0 (stable) coming soon github.com/middyjs/middy/issues github.com/middyjs/middy/issues @loige loige.link/middy-gcs-qa 27
  51. THANKS! THANKS! A special thank you to all the amazing

    Middy users and ! contributors @loige loige.link/middy-gcs loige.link/middy-gcs 29