[{"data":1,"prerenderedAt":2429},["ShallowReactive",2],{"post-cards":3,"categories":733,"post-give-your-lambda-an-http-front-door":783},[4,29,44,61,80,102,113,130,147,161,178,192,207,222,235,244,253,267,281,295,305,318,328,338,349,364,376,389,400,411,422,433,444,455,468,481,496,511,525,539,551,562,573,583,593,603,613,623,633,643,653,663,673,683,693,703,713,723],{"path":5,"title":6,"slug":7,"summary":8,"date":9,"readTime":10,"hasImage":11,"category":12,"tags":17,"tagSlugs":28},"\u002Fposts\u002Fbuild-a-qr-code-lambda-and-call-it-from-laravel","Build a QR Code Lambda and Call It From Laravel","build-a-qr-code-lambda-and-call-it-from-laravel","A hands-on, beginner-friendly build: write a tiny Python AWS Lambda that turns text into a QR code, run it locally in Docker with no AWS account, and call it from a Laravel app. Every line of Python is explained for developers coming from PHP.","2026-06-15",10,true,{"id":13,"name":14,"slug":15,"hue":16},7,"AWS","aws",195,[18,19,22,25],{"name":14,"slug":15},{"name":20,"slug":21},"Lambda","lambda",{"name":23,"slug":24},"Docker","docker",{"name":26,"slug":27},"Laravel","laravel",[15,21,24,27],{"path":30,"title":31,"slug":32,"summary":33,"date":9,"readTime":10,"hasImage":11,"category":34,"tags":35,"tagSlugs":43},"\u002Fposts\u002Fdeploy-a-lambda-container-image-with-ecr-and-the-console","Deploy a Lambda Container Image With ECR and the Console","deploy-a-lambda-container-image-with-ecr-and-the-console","You built a QR code Lambda and ran it locally. Now put it on AWS the click-through way: create an Amazon ECR repository, push your image, and create the Lambda from that image in the console. Then test it and optionally expose it with a Function URL your Laravel app can call.",{"id":13,"name":14,"slug":15,"hue":16},[36,37,38,41,42],{"name":14,"slug":15},{"name":20,"slug":21},{"name":39,"slug":40},"ECR","ecr",{"name":23,"slug":24},{"name":26,"slug":27},[15,21,40,24,27],{"path":45,"title":46,"slug":47,"summary":48,"date":9,"readTime":49,"hasImage":11,"category":50,"tags":55,"tagSlugs":60},"\u002Fposts\u002Fpython-for-php-developers","Python for PHP Developers","python-for-php-developers","A friendly tour of Python for developers who already know modern PHP. We map the things you reach for every day, types, arrays, classes, named arguments, match, and enums, onto their Python equivalents so you can read and write Python with confidence.",12,{"id":51,"name":52,"slug":53,"hue":54},8,"Python","python",330,[56,57],{"name":52,"slug":53},{"name":58,"slug":59},"PHP","php",[53,59],{"path":62,"title":63,"slug":64,"summary":65,"date":66,"readTime":67,"hasImage":11,"category":68,"tags":71,"tagSlugs":79},"\u002Fposts\u002Fsolid-principles-modern-php","SOLID Principles in Modern PHP","solid-principles-modern-php","SOLID has not changed in years, but PHP has. Here are the five object-oriented design principles rewritten for PHP 8.5, using typed properties, readonly, enums, constructor promotion, and property hooks to express the same ideas with far less boilerplate.","2026-06-14T12:00:00",9,{"id":69,"name":58,"slug":59,"hue":70},1,264,[72,73,76],{"name":58,"slug":59},{"name":74,"slug":75},"OOP","oop",{"name":77,"slug":78},"Architecture","architecture",[59,75,78],{"path":81,"title":82,"slug":83,"summary":84,"date":85,"readTime":13,"hasImage":86,"category":87,"tags":92,"tagSlugs":101},"\u002Fposts\u002Fgit-flow-vs-github-flow-choosing-a-branching-strategy","Git Flow vs GitHub Flow: Choosing a Branching Strategy for Your Team","git-flow-vs-github-flow-choosing-a-branching-strategy","Git Flow and GitHub Flow take very different approaches to team branching and releases. Let's compare them, see where trunk-based development fits, and sort out how to handle versioned releases, hotfixes, and everything in between.","2026-06-14",false,{"id":88,"name":89,"slug":90,"hue":91},4,"Git","git",158,[93,95,98],{"name":94,"slug":90},"GIT",{"name":96,"slug":97},"Workflow","workflow",{"name":99,"slug":100},"GitHub","github",[90,97,100],{"path":103,"title":104,"slug":105,"summary":106,"date":85,"readTime":13,"hasImage":86,"category":107,"tags":108,"tagSlugs":112},"\u002Fposts\u002Fgithub-flow-keep-your-main-branch-deployable","GitHub Flow: Keep Your Main Branch Deployable","github-flow-keep-your-main-branch-deployable","GitHub Flow is the lightweight branching workflow built on a single rule: anything in main is deployable. Here is the whole loop, branch, pull request, review, merge and deploy, with the git and gh commands and an honest look at where it fits.",{"id":88,"name":89,"slug":90,"hue":91},[109,110,111],{"name":94,"slug":90},{"name":96,"slug":97},{"name":99,"slug":100},[90,97,100],{"path":114,"title":115,"slug":116,"summary":117,"date":118,"readTime":10,"hasImage":11,"category":119,"tags":120,"tagSlugs":129},"\u002Fposts\u002Forchestrating-lambdas-with-step-functions","Orchestrating Lambdas with Step Functions","orchestrating-lambdas-with-step-functions","Step Functions let you wire Lambdas into workflows with retries, branching, and parallelism, but you do not always need them. Here is an honest guide to when a state machine earns its keep, then a real parallel pipeline built with the modern JSONata syntax, deployed with SAM and tested locally.","2026-06-03",{"id":13,"name":14,"slug":15,"hue":16},[121,122,123,126],{"name":14,"slug":15},{"name":20,"slug":21},{"name":124,"slug":125},"Step Functions","step-functions",{"name":127,"slug":128},"Serverless","serverless",[15,21,125,128],{"path":131,"title":132,"slug":133,"summary":134,"date":135,"readTime":136,"hasImage":11,"category":137,"tags":138,"tagSlugs":146},"\u002Fposts\u002Fgive-your-lambda-an-http-front-door","Give Your Lambda an HTTP Front Door","give-your-lambda-an-http-front-door","Your Lambda works, but how should the world call it? This is a practical tour of the options: invoking directly, Lambda function URLs, and Amazon API Gateway, with a clear guide to what each one buys you. Then we build an HTTP API with SAM, test it locally, and call it from a Laravel app.","2026-05-06",11,{"id":13,"name":14,"slug":15,"hue":16},[139,140,141,144,145],{"name":14,"slug":15},{"name":20,"slug":21},{"name":142,"slug":143},"API Gateway","api-gateway",{"name":127,"slug":128},{"name":26,"slug":27},[15,21,143,128,27],{"path":148,"title":149,"slug":150,"summary":151,"date":152,"readTime":10,"hasImage":11,"category":153,"tags":154,"tagSlugs":160},"\u002Fposts\u002Fpackage-a-python-lambda-as-a-docker-image","Package a Python Lambda as a Docker Image","package-a-python-lambda-as-a-docker-image","AWS Lambda is not just zip files. Here is how to package a Python function as a Docker container image, choose between arm64 and x86_64, test it locally with the Runtime Interface Emulator, push it to Amazon ECR, and invoke it directly without any API Gateway in front.","2026-04-08",{"id":13,"name":14,"slug":15,"hue":16},[155,156,157,158,159],{"name":14,"slug":15},{"name":20,"slug":21},{"name":23,"slug":24},{"name":39,"slug":40},{"name":127,"slug":128},[15,21,24,40,128],{"path":162,"title":163,"slug":164,"summary":165,"date":166,"readTime":167,"hasImage":11,"category":168,"tags":169,"tagSlugs":177},"\u002Fposts\u002Fwhats-new-in-php-8-5","What's New in PHP 8.5","whats-new-in-php-8-5","PHP 8.5 leans into composition and ergonomics. Here are its headline features with practical examples: the pipe operator, cloning with property updates, the NoDiscard attribute, array_first and array_last, the new URI extension, and backtraces on fatal errors.","2025-11-22",6,{"id":69,"name":58,"slug":59,"hue":70},[170,171,174],{"name":58,"slug":59},{"name":172,"slug":173},"PHP 8.5","php-8-5",{"name":175,"slug":176},"What's New","whats-new",[59,173,176],{"path":179,"title":180,"slug":181,"summary":182,"date":183,"readTime":167,"hasImage":11,"category":184,"tags":185,"tagSlugs":191},"\u002Fposts\u002Fwhats-new-in-php-8-4","What's New in PHP 8.4","whats-new-in-php-8-4","PHP 8.4 brought one of the biggest syntax additions of the 8.x line. Here are its headline features with practical examples: property hooks, asymmetric visibility, new without parentheses, the array_find family, the Deprecated attribute, and a modern HTML5 DOM parser.","2024-11-23",{"id":69,"name":58,"slug":59,"hue":70},[186,187,190],{"name":58,"slug":59},{"name":188,"slug":189},"PHP 8.4","php-8-4",{"name":175,"slug":176},[59,189,176],{"path":193,"title":194,"slug":195,"summary":196,"date":197,"readTime":198,"hasImage":86,"category":199,"tags":203,"tagSlugs":206},"\u002Fposts\u002Fstarting-with-rust-installation-first-program","Starting with Rust: From Installation to Your First Program","starting-with-rust-installation-first-program","Learn how to install Rust and write your first \"Hello, world!\" program.","2024-03-23",2,{"id":167,"name":200,"slug":201,"hue":202},"Rust","rust-programming",38,[204],{"name":205,"slug":205},"rust",[205],{"path":208,"title":209,"slug":210,"summary":211,"date":212,"readTime":213,"hasImage":11,"category":214,"tags":215,"tagSlugs":221},"\u002Fposts\u002Fwhats-new-in-php-8-3","What's New in PHP 8.3","whats-new-in-php-8-3","PHP 8.3 is a focused release full of quality-of-life wins. Here are its headline features with practical examples: typed class constants, the Override attribute, json_validate, dynamic constant fetch, random string generation, and readonly deep cloning.","2023-11-25",5,{"id":69,"name":58,"slug":59,"hue":70},[216,217,220],{"name":58,"slug":59},{"name":218,"slug":219},"PHP 8.3","php-8-3",{"name":175,"slug":176},[59,219,176],{"path":223,"title":224,"slug":225,"summary":226,"date":227,"readTime":198,"hasImage":86,"category":228,"tags":232,"tagSlugs":234},"\u002Fposts\u002Fflutter-version-management-fvm","Flutter Version Management","flutter-version-management-fvm","Managing multiple Flutter versions does not need not be a headache. Let's jump into FVM and see how it can simplify your Flutter journey.","2023-10-07",{"id":213,"name":229,"slug":230,"hue":231},"Flutter","flutter",230,[233],{"name":230,"slug":230},[230],{"path":236,"title":237,"slug":238,"summary":239,"date":227,"readTime":69,"hasImage":86,"category":240,"tags":241,"tagSlugs":243},"\u002Fposts\u002Fsetting-up-cocoapods-fvm","Setting Up CocoaPods for FVM-managed Flutter Projects","setting-up-cocoapods-fvm","A guide to installing CocoaPods for a Flutter project while using FVM to manage Flutter versions, ensuring a smooth setup for iOS development.",{"id":213,"name":229,"slug":230,"hue":231},[242],{"name":230,"slug":230},[230],{"path":245,"title":246,"slug":247,"summary":248,"date":227,"readTime":69,"hasImage":86,"category":249,"tags":250,"tagSlugs":252},"\u002Fposts\u002Ftroubleshooting-xcode-15-build-issues-flutter","Troubleshooting Xcode 15 Build Issues in Flutter Projects","troubleshooting-xcode-15-build-issues-flutter","Uncovering solutions to common issues faced when updating to Xcode 15 in a Flutter project using an older version of CocoaPods.",{"id":213,"name":229,"slug":230,"hue":231},[251],{"name":230,"slug":230},[230],{"path":254,"title":255,"slug":256,"summary":257,"date":258,"readTime":167,"hasImage":11,"category":259,"tags":260,"tagSlugs":266},"\u002Fposts\u002Fwhats-new-in-php-8-2","What's New in PHP 8.2","whats-new-in-php-8-2","PHP 8.2 polished the type system and the immutability story. Here are its headline features with practical examples: readonly classes, DNF types, standalone null, false and true types, the new Random extension, constants in traits, and sensitive parameter redaction.","2022-12-10",{"id":69,"name":58,"slug":59,"hue":70},[261,262,265],{"name":58,"slug":59},{"name":263,"slug":264},"PHP 8.2","php-8-2",{"name":175,"slug":176},[59,264,176],{"path":268,"title":269,"slug":270,"summary":271,"date":272,"readTime":167,"hasImage":11,"category":273,"tags":274,"tagSlugs":280},"\u002Fposts\u002Fwhats-new-in-php-8-1","What's New in PHP 8.1","whats-new-in-php-8-1","PHP 8.1 is one of the most loved releases of the 8.x line. Here are its headline features with practical examples: enums, readonly properties, first-class callable syntax, fibers, the never return type, and new in initializers.","2021-11-27",{"id":69,"name":58,"slug":59,"hue":70},[275,276,279],{"name":58,"slug":59},{"name":277,"slug":278},"PHP 8.1","php-8-1",{"name":175,"slug":176},[59,278,176],{"path":282,"title":283,"slug":284,"summary":285,"date":286,"readTime":167,"hasImage":11,"category":287,"tags":288,"tagSlugs":294},"\u002Fposts\u002Fwhats-new-in-php-8-0","What's New in PHP 8.0","whats-new-in-php-8-0","PHP 8.0 was a true major version. Here is a tour of its headline features with practical examples: constructor property promotion, named arguments, the match expression, the nullsafe operator, union types, and string helpers that finally read like English.","2020-11-28",{"id":69,"name":58,"slug":59,"hue":70},[289,290,293],{"name":58,"slug":59},{"name":291,"slug":292},"PHP 8.0","php-8-0",{"name":175,"slug":176},[59,292,176],{"path":296,"title":297,"slug":298,"summary":299,"date":300,"readTime":88,"hasImage":86,"category":301,"tags":302,"tagSlugs":304},"\u002Fposts\u002Fgit-tracking-a-remote-branch-upstream-for-changes","Git: Tracking a Remote Branch for Changes","git-tracking-a-remote-branch-upstream-for-changes","When you fork a project, you need a way to pull in changes from the original repository, usually called upstream. Here is how to wire up an upstream remote, actually sync your fork, and set up branch tracking so plain git pull and git push just work.","2018-11-04",{"id":88,"name":89,"slug":90,"hue":91},[303],{"name":94,"slug":90},[90],{"path":306,"title":307,"slug":308,"summary":309,"date":300,"readTime":310,"hasImage":11,"category":311,"tags":315,"tagSlugs":317},"\u002Fposts\u002Fjavascript-array-map-filter-reduce-functions","JavaScript's map, filter, and reduce methods","javascript-array-map-filter-reduce-functions","JavaScript provides some amazing functions that can be called against your arrays to help filter them, manipulate them, or even reduce them down to a single value or grouped values.",3,{"id":198,"name":312,"slug":313,"hue":314},"JavaScript","javascript",92,[316],{"name":312,"slug":313},[313],{"path":319,"title":320,"slug":321,"summary":322,"date":323,"readTime":69,"hasImage":11,"category":324,"tags":325,"tagSlugs":327},"\u002Fposts\u002Fphp-fizzbuzz-example","FizzBuzz in PHP: A Fresh Approach","php-fizzbuzz-example","FizzBuzz is a very popular programming question that tests your logic to see if you can build a simple program.","2018-11-02",{"id":69,"name":58,"slug":59,"hue":70},[326],{"name":58,"slug":59},[59],{"path":329,"title":330,"slug":331,"summary":332,"date":333,"readTime":198,"hasImage":11,"category":334,"tags":335,"tagSlugs":337},"\u002Fposts\u002Fphp-array-reduce","PHP's array_reduce is not only for outputting single values","php-array-reduce","PHP's array_reduce is a simple way to partition a set of data or return a single value. It is super powerful and worth spending time learning.","2018-11-01",{"id":69,"name":58,"slug":59,"hue":70},[336],{"name":58,"slug":59},[59],{"path":339,"title":340,"slug":341,"summary":342,"date":343,"readTime":167,"hasImage":86,"category":344,"tags":345,"tagSlugs":348},"\u002Fposts\u002Fimprove-your-git-workflow-with-git-flow","Improve Your Git Workflow with Git Flow","improve-your-git-workflow-with-git-flow","Git Flow is a structured branching model built around versioned, scheduled releases. Here is how its branches fit together, a hands-on walkthrough of features, releases and hotfixes, and an honest take on when it is still the right call.","2016-12-06",{"id":88,"name":89,"slug":90,"hue":91},[346,347],{"name":94,"slug":90},{"name":96,"slug":97},[90,97],{"path":350,"title":351,"slug":352,"summary":353,"date":354,"readTime":167,"hasImage":86,"category":355,"tags":359,"tagSlugs":363},"\u002Fposts\u002Fusing-css-transitions","Using CSS Transitions","using-css-transitions","CSS transitions are the standard way to apply transitions to your elements, and have been for years, replacing the old approach of using JavaScript. In this article, I'll go through each of the transition properties available, and provide examples of how to use them.","2016-12-05",{"id":310,"name":356,"slug":357,"hue":358},"HTML & CSS","html-css",55,[360],{"name":361,"slug":362},"CSS","css",[362],{"path":365,"title":366,"slug":367,"summary":368,"date":369,"readTime":88,"hasImage":86,"category":370,"tags":371,"tagSlugs":375},"\u002Fposts\u002Fstructuring-your-website-with-html-5-semantics","Structuring Your Website With HTML 5 Semantics","structuring-your-website-with-html-5-semantics","Prior to HTML 5, there was no real markup to help explain the intent behind your HTML code. The goal of HTML 5 was to offer a more readable way of writing your code, so that any author that comes after you can have an easier time going through what you've created.","2016-12-04",{"id":310,"name":356,"slug":357,"hue":358},[372],{"name":373,"slug":374},"HTML","html",[374],{"path":377,"title":378,"slug":379,"summary":380,"date":381,"readTime":198,"hasImage":86,"category":382,"tags":383,"tagSlugs":388},"\u002Fposts\u002Finterpolation-in-stylus-css-pre-processor","Interpolation in Stylus","interpolation-in-stylus-css-pre-processor","You can also use interpolation to improve your functions for reuse, as well as your other code within your stylesheet. The way it works is that you can wrap your expression within {}, which will then be outputted as the identifier.","2016-12-03",{"id":310,"name":356,"slug":357,"hue":358},[384,387],{"name":385,"slug":386},"Stylus","stylus",{"name":361,"slug":362},[386,362],{"path":390,"title":391,"slug":392,"summary":393,"date":394,"readTime":69,"hasImage":86,"category":395,"tags":396,"tagSlugs":399},"\u002Fposts\u002Fcreating-configuration-files-in-stylus-css-pre-processor","Creating Configuration Files In Stylus","creating-configuration-files-in-stylus-css-pre-processor","It's super simple to create a configuration file for instance that would manage your media query break points. You could also use a configuration file for managing colors, font sizes, and other variables such as gutter spacing and more.","2016-12-02",{"id":310,"name":356,"slug":357,"hue":358},[397,398],{"name":361,"slug":362},{"name":385,"slug":386},[362,386],{"path":401,"title":402,"slug":403,"summary":404,"date":405,"readTime":88,"hasImage":86,"category":406,"tags":407,"tagSlugs":410},"\u002Fposts\u002Fusing-functions-and-mixins-with-stylus-css-pre-processor","Using Functions and Mixins with Stylus","using-functions-and-mixins-with-stylus-css-pre-processor","Stylus allows you to create functions and mixins of reusable code for your stylesheets. You can also handle mathematical operations, unary operations, and more allowing you complete control over your stylesheets with ease.","2016-12-01",{"id":310,"name":356,"slug":357,"hue":358},[408,409],{"name":361,"slug":362},{"name":385,"slug":386},[362,386],{"path":412,"title":413,"slug":414,"summary":415,"date":416,"readTime":198,"hasImage":86,"category":417,"tags":418,"tagSlugs":421},"\u002Fposts\u002Fsetting-variables-in-stylus-css-pre-processor","Setting Variables in Stylus","setting-variables-in-stylus-css-pre-processor","Unlike CSS, in Stylus you can assign expressions to variables that can be reusable throughout your stylesheets.","2016-11-29",{"id":310,"name":356,"slug":357,"hue":358},[419,420],{"name":361,"slug":362},{"name":385,"slug":386},[362,386],{"path":423,"title":424,"slug":425,"summary":426,"date":427,"readTime":213,"hasImage":86,"category":428,"tags":429,"tagSlugs":432},"\u002Fposts\u002Fusing-selectors-in-stylus-css-pre-processor","Using Selectors in Stylus","using-selectors-in-stylus-css-pre-processor","Selectors are a way to pick the elements that you want styled. In Stylus, similar to CSS, you can apply a set of styles to any element by separating them by a comma delimited list. Stylus though, also allows you to select multiple elements by separating each on their own line.","2016-11-28",{"id":310,"name":356,"slug":357,"hue":358},[430,431],{"name":361,"slug":362},{"name":385,"slug":386},[362,386],{"path":434,"title":435,"slug":436,"summary":437,"date":438,"readTime":69,"hasImage":86,"category":439,"tags":440,"tagSlugs":443},"\u002Fposts\u002Flearning-stylus-a-css-pre-processor","Learning Stylus: A CSS Pre-Processor","learning-stylus-a-css-pre-processor","This mini-series will be a little different to how you may see other articles on my site. Really this article is more geared as notes for me as I go through the documentation for Stylus, and learn the ins and outs of this beautiful language.","2016-11-27",{"id":310,"name":356,"slug":357,"hue":358},[441,442],{"name":361,"slug":362},{"name":385,"slug":386},[362,386],{"path":445,"title":446,"slug":447,"summary":448,"date":449,"readTime":88,"hasImage":86,"category":450,"tags":451,"tagSlugs":454},"\u002Fposts\u002Fbem-methodology-overview-and-naming-conventions","BEM Methodology Overview and Naming Conventions","bem-methodology-overview-and-naming-conventions","BEM or Block Element Modifier is a naming convention used to help organize your code base. In this article, I discuss its uses within your CSS projects.","2016-11-26",{"id":310,"name":356,"slug":357,"hue":358},[452,453],{"name":361,"slug":362},{"name":373,"slug":374},[362,374],{"path":456,"title":457,"slug":458,"summary":459,"date":460,"readTime":69,"hasImage":86,"category":461,"tags":462,"tagSlugs":467},"\u002Fposts\u002Fintroduction-to-ecmascript-6","Introduction to ECMAScript 6","introduction-to-ecmascript-6","The latest in ECMAScript 6 introduces new features to JavaScript which makes it so much more fun to use, while solving problems that have been around for years. The intent of this article is to provide you with resources you can use to start learning ES6 today.","2016-11-25",{"id":198,"name":312,"slug":313,"hue":314},[463,464],{"name":312,"slug":313},{"name":465,"slug":466},"ECMAScript","ecmascript",[313,466],{"path":469,"title":470,"slug":471,"summary":472,"date":473,"readTime":310,"hasImage":86,"category":474,"tags":475,"tagSlugs":480},"\u002Fposts\u002Fbabel-installation-and-configuration","Babel Installation and Configuration","babel-installation-and-configuration","Babel offers a convenient way to transform your ES6 code to JavaScript that all browsers can understand. In this article we'll go over a basic configuration that will enable you to start using it with any project right away.","2016-11-24",{"id":198,"name":312,"slug":313,"hue":314},[476,477],{"name":312,"slug":313},{"name":478,"slug":479},"Babel","babel",[313,479],{"path":482,"title":483,"slug":484,"summary":485,"date":486,"readTime":69,"hasImage":86,"category":487,"tags":488,"tagSlugs":495},"\u002Fposts\u002Fconfiguring-stylus-css-pre-processor-with-gulp-and-sourcemaps","Configuring Stylus CSS Pre-Processor with Gulp and Sourcemaps","configuring-stylus-css-pre-processor-with-gulp-and-sourcemaps","In this article we'll go over how to configure your project to process Stylus files using Gulp. We'll also create source map file which your browser will use to help point you in the right direction of your files when developing","2016-11-23",{"id":198,"name":312,"slug":313,"hue":314},[489,490,491,492],{"name":312,"slug":313},{"name":385,"slug":386},{"name":361,"slug":362},{"name":493,"slug":494},"Gulp","gulp",[313,386,362,494],{"path":497,"title":498,"slug":499,"summary":500,"date":501,"readTime":198,"hasImage":86,"category":502,"tags":503,"tagSlugs":510},"\u002Fposts\u002Fconfiguring-gulp-with-less-css-pre-processor","Configuring Gulp With Less CSS Pre-Processor","configuring-gulp-with-less-css-pre-processor","Less is a CSS pre-processor allowing you to create variables, mixins, and functions in an effort to make your CSS more maintainable.","2016-11-22",{"id":198,"name":312,"slug":313,"hue":314},[504,505,506,509],{"name":493,"slug":494},{"name":312,"slug":313},{"name":507,"slug":508},"Less","less",{"name":361,"slug":362},[494,313,508,362],{"path":512,"title":513,"slug":514,"summary":515,"date":516,"readTime":198,"hasImage":11,"category":517,"tags":518,"tagSlugs":524},"\u002Fposts\u002Fusing-browser-sync-with-gulp-for-live-reloading","Using Browser Sync with Gulp for Live Reloading","using-browser-sync-with-gulp-for-live-reloading","Browser Sync is a nice tool to use while developing. It allows your browser to reload live when changes are made to your files. For instance, assuming we're watching our CSS file for changes we can have the browser auto refresh\u002Fsync when it sees those changes made.","2016-11-21",{"id":198,"name":312,"slug":313,"hue":314},[519,520,523],{"name":312,"slug":313},{"name":521,"slug":522},"Browser Sync","browser-sync",{"name":493,"slug":494},[313,522,494],{"path":526,"title":527,"slug":528,"summary":529,"date":530,"readTime":198,"hasImage":86,"category":531,"tags":532,"tagSlugs":538},"\u002Fposts\u002Fgulp-watch-automate-your-gulp-tasks","Gulp Watch: Automate Your Gulp Tasks","gulp-watch-automate-your-gulp-tasks","Gulp watch is perfect for when you're editing project files since it allows you to not have to run the gulp command manually each time.","2016-11-20",{"id":198,"name":312,"slug":313,"hue":314},[533,534,537],{"name":312,"slug":313},{"name":535,"slug":536},"Yarn","yarn",{"name":493,"slug":494},[313,536,494],{"path":540,"title":541,"slug":542,"summary":543,"date":544,"readTime":167,"hasImage":86,"category":545,"tags":546,"tagSlugs":550},"\u002Fposts\u002Fconfiguring-gulp-on-a-new-project","Configuring Gulp On A New Project","configuring-gulp-on-a-new-project","Gulp may seem like a scary thing to wrap your head around at first, but it's actually quite easy to start using once you understand the basics.","2016-11-19",{"id":198,"name":312,"slug":313,"hue":314},[547,548,549],{"name":312,"slug":313},{"name":493,"slug":494},{"name":535,"slug":536},[313,494,536],{"path":552,"title":553,"slug":554,"summary":555,"date":556,"readTime":310,"hasImage":86,"category":557,"tags":558,"tagSlugs":561},"\u002Fposts\u002Fyarn-publishing-a-package","Yarn: Publishing a Package","yarn-publishing-a-package","Publishing a package to the npm repository has never been simpler. With a few steps, you can create a package that is redistributable to all of your projects.","2016-11-18",{"id":198,"name":312,"slug":313,"hue":314},[559,560],{"name":312,"slug":313},{"name":535,"slug":536},[313,536],{"path":563,"title":564,"slug":565,"summary":566,"date":567,"readTime":310,"hasImage":86,"category":568,"tags":569,"tagSlugs":572},"\u002Fposts\u002Fyarn-fast-and-secure-dependency-management","Yarn: Fast and Secure Dependency Management","yarn-fast-and-secure-dependency-management","Yarn is a super simple dependency management tool which is way faster to use instead of traditional npm. It acts as a drop-in replacement, so you can get started using yarn right away. The best way to install yarn is by using npm.","2016-11-17",{"id":198,"name":312,"slug":313,"hue":314},[570,571],{"name":312,"slug":313},{"name":535,"slug":536},[313,536],{"path":574,"title":575,"slug":576,"summary":577,"date":578,"readTime":69,"hasImage":11,"category":579,"tags":580,"tagSlugs":582},"\u002Fposts\u002Fsupport-for-keys-in-list-or-its-new-shorthand-syntax-in-php","Support for keys in list(), or its new shorthand syntax [] in PHP","support-for-keys-in-list-or-its-new-shorthand-syntax-in-php","Now as of PHP 7.1, you can define the keys of your array that will be parsed when destructuring your arrays. Prior to PHP 7.1, you could only use arrays with numeric indexes. Now with this new addition, our lives just got easier.","2016-11-16",{"id":69,"name":58,"slug":59,"hue":70},[581],{"name":58,"slug":59},[59],{"path":584,"title":585,"slug":586,"summary":587,"date":588,"readTime":69,"hasImage":11,"category":589,"tags":590,"tagSlugs":592},"\u002Fposts\u002Ftype-hinting-with-the-iterable-pseudo-type-in-php","Type Hinting With The Iterable pseudo-type In PHP","type-hinting-with-the-iterable-pseudo-type-in-php","As of PHP 7.1, you can now type hint your method\u002Ffunction arguments with the keyword iterable for handling arrays or even objects that implement the Traversable interface.","2016-11-15",{"id":69,"name":58,"slug":59,"hue":70},[591],{"name":58,"slug":59},[59],{"path":594,"title":595,"slug":596,"summary":597,"date":598,"readTime":69,"hasImage":11,"category":599,"tags":600,"tagSlugs":602},"\u002Fposts\u002Ftype-hinting-callable-functions-in-php","Type Hinting Callable Functions in PHP","type-hinting-callable-functions-in-php","As of PHP 5.4, you can type hint your method arguments with the callable keyword allowing you to enforce the type of data that is passed via your arguments.","2016-11-14",{"id":69,"name":58,"slug":59,"hue":70},[601],{"name":58,"slug":59},[59],{"path":604,"title":605,"slug":606,"summary":607,"date":608,"readTime":69,"hasImage":11,"category":609,"tags":610,"tagSlugs":612},"\u002Fposts\u002Fsetting-visibility-for-your-class-constants-in-php","Setting Visibility for Your Class Constants in PHP","setting-visibility-for-your-class-constants-in-php","Now in PHP 7.1+, you can set different visibility modifiers for each of your class constants. The available visibility modifiers consist of public, protected, and private.","2016-11-13",{"id":69,"name":58,"slug":59,"hue":70},[611],{"name":58,"slug":59},[59],{"path":614,"title":615,"slug":616,"summary":617,"date":618,"readTime":69,"hasImage":11,"category":619,"tags":620,"tagSlugs":622},"\u002Fposts\u002Fanonymous-classes-php","Using Anonymous Classes in PHP","anonymous-classes-php","As of PHP 7, you can now create quick throwaway objects for use within your projects. This can be especially useful for your automated tests, for instance, with allowing you to create quick implementations of your interfaces.","2016-11-12",{"id":69,"name":58,"slug":59,"hue":70},[621],{"name":58,"slug":59},[59],{"path":624,"title":625,"slug":626,"summary":627,"date":628,"readTime":69,"hasImage":11,"category":629,"tags":630,"tagSlugs":632},"\u002Fposts\u002Fsymmetric-array-destructuring-in-php","Symmetric Array Destructuring in PHP","symmetric-array-destructuring-in-php","As of PHP 7.1, you can now use the shorthand array syntax to destructure your arrays for assignment. Previously you would have had to use a function like list, but now you can use the simple new array shorthand syntax.","2016-11-11",{"id":69,"name":58,"slug":59,"hue":70},[631],{"name":58,"slug":59},[59],{"path":634,"title":635,"slug":636,"summary":637,"date":638,"readTime":198,"hasImage":11,"category":639,"tags":640,"tagSlugs":642},"\u002Fposts\u002Fphp-array-map-to-format-your-arrays-without-loops","Using PHP's array_map to format your arrays without loops","php-array-map-to-format-your-arrays-without-loops","So let's face it, loops are a bit boring. So how can we mix it up? Let's assume we have a case where we have a CSV file that we want to quickly parse.","2016-11-10",{"id":69,"name":58,"slug":59,"hue":70},[641],{"name":58,"slug":59},[59],{"path":644,"title":645,"slug":646,"summary":647,"date":648,"readTime":13,"hasImage":11,"category":649,"tags":650,"tagSlugs":652},"\u002Fposts\u002Fsolid-principles-in-php","SOLID Principles in PHP","solid-principles-in-php","The 5 basic principles for Object-Oriented Design, SOLID, were first created in an effort to improve maintainability in our code bases. SOLID is a mnemonic acronym that stands for each of the following principles: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion.","2016-11-09",{"id":69,"name":58,"slug":59,"hue":70},[651],{"name":58,"slug":59},[59],{"path":654,"title":655,"slug":656,"summary":657,"date":658,"readTime":69,"hasImage":11,"category":659,"tags":660,"tagSlugs":662},"\u002Fposts\u002Ffiltering-arrays-without-using-loops-in-php","Filtering Arrays Without Using Loops in PHP","filtering-arrays-without-using-loops-in-php","PHP has a built-in function called array_filter that allows you to filter through your arrays without the need for a loop. Personally, this approach feels much cleaner to me and simpler to comprehend.","2016-11-08",{"id":69,"name":58,"slug":59,"hue":70},[661],{"name":58,"slug":59},[59],{"path":664,"title":665,"slug":666,"summary":667,"date":668,"readTime":69,"hasImage":11,"category":669,"tags":670,"tagSlugs":672},"\u002Fposts\u002Fvoid-return-types-in-php","Void Return Types in PHP","void-return-types-in-php","As of PHP 7.1, we can now use void return types within our methods. This is useful for cases where you have methods that are just setting or processing data without the need of returning any values.","2016-11-07",{"id":69,"name":58,"slug":59,"hue":70},[671],{"name":58,"slug":59},[59],{"path":674,"title":675,"slug":676,"summary":677,"date":678,"readTime":69,"hasImage":11,"category":679,"tags":680,"tagSlugs":682},"\u002Fposts\u002Ftype-hinting-with-nullable-types-in-php","Type Hinting with Nullable Types in PHP","type-hinting-with-nullable-types-in-php","As of PHP 7.1, you can now set your type declarations as nullable by simply prefixing them with a question mark ?. In doing so a null value can be passed in as a parameter or returned as a value for your methods.","2016-11-06",{"id":69,"name":58,"slug":59,"hue":70},[681],{"name":58,"slug":59},[59],{"path":684,"title":685,"slug":686,"summary":687,"date":688,"readTime":69,"hasImage":11,"category":689,"tags":690,"tagSlugs":692},"\u002Fposts\u002Fphp-group-multiple-use-declarations","PHP Group Multiple use Declarations","php-group-multiple-use-declarations","As of PHP 7, you can now group your imported classes, functions, and constants from under the same namespace.","2016-11-05",{"id":69,"name":58,"slug":59,"hue":70},[691],{"name":58,"slug":59},[59],{"path":694,"title":695,"slug":696,"summary":697,"date":698,"readTime":69,"hasImage":11,"category":699,"tags":700,"tagSlugs":702},"\u002Fposts\u002Fphp-null-coalescing-operator","PHP Null Coalescing Operator","php-null-coalescing-operator","One of my new favorite additions to PHP 7, is the Null Coalescing Operator. It cleans up your code by removing a tedious step of checking if some value is isset() and not NULL and returning it or if not setting a default.","2016-11-04",{"id":69,"name":58,"slug":59,"hue":70},[701],{"name":58,"slug":59},[59],{"path":704,"title":705,"slug":706,"summary":707,"date":708,"readTime":198,"hasImage":11,"category":709,"tags":710,"tagSlugs":712},"\u002Fposts\u002Fphp-spaceship-operator","PHP Spaceship Operator","php-spaceship-operator","One of the new features to hit PHP 7 is the Spaceship Operator. This new trick helps improve the way you'd compare 2 expressions. In short, the comparison returns 1 of 3 values (-1, 0, or 1) depending on the result of the comparison.","2016-11-03",{"id":69,"name":58,"slug":59,"hue":70},[711],{"name":58,"slug":59},[59],{"path":714,"title":715,"slug":716,"summary":717,"date":718,"readTime":310,"hasImage":11,"category":719,"tags":720,"tagSlugs":722},"\u002Fposts\u002Freturn-type-declarations-in-php","Return Type Declarations in PHP","return-type-declarations-in-php","PHP 7 now makes it possible to declare return types for your methods. This allows you better control over the data that will be returned from each method in your application.","2016-11-02",{"id":69,"name":58,"slug":59,"hue":70},[721],{"name":58,"slug":59},[59],{"path":724,"title":725,"slug":726,"summary":727,"date":728,"readTime":69,"hasImage":11,"category":729,"tags":730,"tagSlugs":732},"\u002Fposts\u002Fscalar-type-hints-php","Scalar Type Hints in PHP","scalar-type-hints-php","Starting with PHP 7.0, it's now possible to declare scalar type hints for your method arguments. Previously, we were able to use array and callable, but now with PHP 7+, we have much more control.","2016-11-01",{"id":69,"name":58,"slug":59,"hue":70},[731],{"name":58,"slug":59},[59],[734,741,747,753,759,765,771,777],{"id":735,"description":736,"extension":737,"hue":70,"meta":738,"name":58,"slug":59,"stem":739,"weight":69,"__hash__":740},"categories\u002Fcategories\u002Fphp.json","PHP articles and tutorials ranging from new language features to using interesting packages.","json",{},"categories\u002Fphp","h_EmN4YMO4b2mBt3MPLs7RvscJx0NBmwDIZPxqPqKLE",{"id":742,"description":743,"extension":737,"hue":314,"meta":744,"name":312,"slug":313,"stem":745,"weight":198,"__hash__":746},"categories\u002Fcategories\u002Fjavascript.json","JavaScript articles and tutorials ranging from new language features to using interesting packages.",{},"categories\u002Fjavascript","7gmVgkw5BRo26i1bFoSv96bwDJ4nTtZcJ9Ud6u5p0yk",{"id":748,"description":749,"extension":737,"hue":358,"meta":750,"name":356,"slug":357,"stem":751,"weight":310,"__hash__":752},"categories\u002Fcategories\u002Fhtml-css.json","HTML & CSS articles and tutorials ranging from new language features to using interesting packages.",{},"categories\u002Fhtml-css","vXvPlRA-iaeCJ64Wi3sLyUR0kqL48zYcZWORRqt8N70",{"id":754,"description":755,"extension":737,"hue":91,"meta":756,"name":89,"slug":90,"stem":757,"weight":88,"__hash__":758},"categories\u002Fcategories\u002Fgit.json","Git articles and tutorials ranging from new language features to different workflows.",{},"categories\u002Fgit","qOqFsFTKI9XB444UodUKW_3AakFadHzW-ss8V-maUmE",{"id":760,"description":761,"extension":737,"hue":231,"meta":762,"name":229,"slug":230,"stem":763,"weight":213,"__hash__":764},"categories\u002Fcategories\u002Fflutter.json","Dive into Flutter, the open-source UI software development toolkit, as we explore its capabilities in creating natively compiled applications for mobile, web, and desktop from a single codebase.",{},"categories\u002Fflutter","aD1moU8CgoYt4FRnSeA4Iy9xxnnopdEKBEYP2arAzdI",{"id":766,"description":767,"extension":737,"hue":202,"meta":768,"name":200,"slug":201,"stem":769,"weight":167,"__hash__":770},"categories\u002Fcategories\u002Frust-programming.json","From setting up your environment to advanced concepts, this is your go-to resource for all things Rust.",{},"categories\u002Frust-programming","LscnqSsk-htWc9yZg9eXaIUJwNfTK5oaZOClYKagNC4",{"id":772,"description":773,"extension":737,"hue":16,"meta":774,"name":14,"slug":15,"stem":775,"weight":13,"__hash__":776},"categories\u002Fcategories\u002Faws.json","Hands-on AWS for builders: Lambda, containers and ECR, API Gateway, Step Functions, and the serverless glue in between.",{},"categories\u002Faws","gU2fpFeHDrBz8RJy54lYK7NJxCnMyma_fblrxDoJByQ",{"id":778,"description":779,"extension":737,"hue":54,"meta":780,"name":52,"slug":53,"stem":781,"weight":51,"__hash__":782},"categories\u002Fcategories\u002Fpython.json","Python for people who already build software: a practical, PHP-developer-friendly path into the language and its ecosystem.",{},"categories\u002Fpython","B6ssFzfg4dLAIzOltx3jcPOk7qghiDxoDD74rhlQ9kU",{"id":784,"title":132,"body":785,"category":2415,"date":135,"description":2416,"extension":2417,"hasImage":11,"meta":2418,"navigation":11,"path":131,"readTime":136,"seo":2419,"slug":133,"stem":2420,"summary":134,"tagSlugs":2421,"tags":2422,"__hash__":2428},"posts\u002Fposts\u002Fgive-your-lambda-an-http-front-door.md",{"type":786,"value":787,"toc":2405},"minimark",[788,798,803,806,809,813,821,866,880,965,973,977,980,990,1012,1015,1039,1042,1046,1053,1288,1295,1318,1329,1333,1352,1862,1889,1893,1899,1961,1972,1976,1979,1994,2004,2056,2063,2067,2074,2077,2198,2201,2382,2385,2388,2401],[789,790,791,792,797],"p",{},"In ",[793,794,796],"a",{"href":795},"\u002Farticles\u002Fpackage-a-python-lambda-as-a-docker-image","part one"," we packaged a Python thumbnailer as a container image, pushed it to ECR, and invoked it directly. Direct invocation is perfect for internal tools and event-driven work, but it needs AWS credentials and request signing on every call, so it is not how a browser, a webhook, or a third party reaches your function. For that you want a plain HTTPS endpoint. Lambda gives you two front doors, and knowing which to pick (and when you need neither) is most of the battle.",[799,800,802],"h2",{"id":801},"you-already-have-one-way-in-direct-invocation","You already have one way in: direct invocation",[789,804,805],{},"Worth saying plainly, because it is easy to forget: API Gateway is just one of many ways to trigger a Lambda. You can invoke it directly with the SDK or CLI, and in production it is far more often triggered by events: an S3 upload, an SQS message, an EventBridge schedule, a DynamoDB stream. None of those involve API Gateway at all.",[789,807,808],{},"Direct invocation shines when the caller is your own backend and already holds AWS credentials. From a server-side app, signing the request is automatic and there is no public surface to secure. The moment the caller is a browser or someone else's system, though, you want HTTP.",[799,810,812],{"id":811},"the-simplest-http-lambda-function-urls","The simplest HTTP: Lambda function URLs",[789,814,815,816,820],{},"A function URL is a dedicated HTTPS endpoint bolted straight onto one function, with no API Gateway in the picture. You get a permanent address shaped like ",[817,818,819],"code",{},"https:\u002F\u002F\u003Curl-id>.lambda-url.\u003Cregion>.on.aws",". The quickest way to add one:",[822,823,828],"pre",{"className":824,"code":825,"language":826,"meta":827,"style":827},"language-bash shiki shiki-themes github-dark github-dark","aws lambda create-function-url-config \\\n  --function-name thumbnailer \\\n  --auth-type NONE\n","bash","",[817,829,830,848,858],{"__ignoreMap":827},[831,832,834,837,841,844],"span",{"class":833,"line":69},"line",[831,835,15],{"class":836},"sFR8T",[831,838,840],{"class":839},"s4wv1"," lambda",[831,842,843],{"class":839}," create-function-url-config",[831,845,847],{"class":846},"s8ozJ"," \\\n",[831,849,850,853,856],{"class":833,"line":198},[831,851,852],{"class":846},"  --function-name",[831,854,855],{"class":839}," thumbnailer",[831,857,847],{"class":846},[831,859,860,863],{"class":833,"line":310},[831,861,862],{"class":846},"  --auth-type",[831,864,865],{"class":839}," NONE\n",[789,867,868,871,872,875,876,879],{},[817,869,870],{},"AuthType"," is either ",[817,873,874],{},"NONE"," (anyone with the URL can call it, good for public endpoints and webhooks) or ",[817,877,878],{},"AWS_IAM"," (callers must sign requests with IAM credentials). CORS is built in, and for repeatable infrastructure you would define the URL in CloudFormation rather than the CLI:",[822,881,885],{"className":882,"code":883,"language":884,"meta":827,"style":827},"language-yaml shiki shiki-themes github-dark github-dark","Type: AWS::Lambda::Url\nProperties:\n  AuthType: NONE\n  TargetFunctionArn: !Ref Thumbnailer\n  Cors:\n    AllowOrigins: [\"*\"]\n    AllowMethods: [\"POST\"]\n","yaml",[817,886,887,900,908,918,932,939,953],{"__ignoreMap":827},[831,888,889,893,897],{"class":833,"line":69},[831,890,892],{"class":891},"sxg3X","Type",[831,894,896],{"class":895},"suv1-",": ",[831,898,899],{"class":839},"AWS::Lambda::Url\n",[831,901,902,905],{"class":833,"line":198},[831,903,904],{"class":891},"Properties",[831,906,907],{"class":895},":\n",[831,909,910,913,915],{"class":833,"line":310},[831,911,912],{"class":891},"  AuthType",[831,914,896],{"class":895},[831,916,917],{"class":839},"NONE\n",[831,919,920,923,925,929],{"class":833,"line":88},[831,921,922],{"class":891},"  TargetFunctionArn",[831,924,896],{"class":895},[831,926,928],{"class":927},"sOPea","!Ref",[831,930,931],{"class":839}," Thumbnailer\n",[831,933,934,937],{"class":833,"line":213},[831,935,936],{"class":891},"  Cors",[831,938,907],{"class":895},[831,940,941,944,947,950],{"class":833,"line":167},[831,942,943],{"class":891},"    AllowOrigins",[831,945,946],{"class":895},": [",[831,948,949],{"class":839},"\"*\"",[831,951,952],{"class":895},"]\n",[831,954,955,958,960,963],{"class":833,"line":13},[831,956,957],{"class":891},"    AllowMethods",[831,959,946],{"class":895},[831,961,962],{"class":839},"\"POST\"",[831,964,952],{"class":895},[789,966,967,968,972],{},"The lovely part: function URLs deliver events in the ",[969,970,971],"strong",{},"same payload format 2.0"," as API Gateway HTTP APIs, so the handler code is identical whether you front it with a function URL or a full API. The limits are real, though. You get one function and one route, no custom domain, no API keys or usage plans, and no AWS WAF. Throttling exists only indirectly: you cap concurrency with reserved concurrency, and your ceiling is roughly ten requests per second for each unit of it. A function URL is the right call for a single endpoint, a webhook receiver, or an internal service. For a real API you graduate to API Gateway.",[799,974,976],{"id":975},"what-api-gateway-actually-buys-you","What API Gateway actually buys you",[789,978,979],{},"API Gateway sits in front of one or many Lambdas and adds everything an API needs around the raw function. It comes in two flavors, and the choice mostly comes down to which features you need.",[789,981,982,985,986,989],{},[969,983,984],{},"HTTP API"," is the newer, cheaper, lower-latency option, and the right default for a straightforward Lambda proxy. ",[969,987,988],{},"REST API"," is the older, fuller, pricier one. Here is the split that matters in practice:",[991,992,993,1000,1006],"ul",{},[994,995,996,999],"li",{},[969,997,998],{},"Both HTTP and REST give you:"," routing across many paths, methods, and functions; stages; custom domain names with managed TLS; CORS; authorizers for IAM, Amazon Cognito, and custom Lambda logic; mutual TLS; CloudWatch metrics and access logs.",[994,1001,1002,1005],{},[969,1003,1004],{},"HTTP API adds:"," native JWT authorizers and automatic deployments, at a noticeably lower price.",[994,1007,1008,1011],{},[969,1009,1010],{},"REST API alone gives you:"," API keys and usage plans, per-client rate limiting, request validation, AWS WAF integration, response caching, edge-optimized and private endpoints, request body transformation, and X-Ray tracing.",[789,1013,1014],{},"So the decision guide reads like this:",[991,1016,1017,1023,1029,1034],{},[994,1018,1019,1022],{},[969,1020,1021],{},"Invoke directly"," (SDK or CLI) when the caller is your own credentialed backend or an AWS event source.",[994,1024,1025,1028],{},[969,1026,1027],{},"Function URL"," when you need the simplest possible public HTTPS for a single function, and you do not need keys, custom domains, or WAF.",[994,1030,1031,1033],{},[969,1032,984],{}," for a real web API with routes, a custom domain, and JWT or Cognito auth, at the lowest cost. This covers most applications.",[994,1035,1036,1038],{},[969,1037,988],{}," when you specifically need API keys and usage plans, request validation, WAF, caching, or private endpoints.",[789,1040,1041],{},"For the thumbnailer, an HTTP API is the sweet spot, so that is what we will build.",[799,1043,1045],{"id":1044},"build-the-http-api-with-sam","Build the HTTP API with SAM",[789,1047,1048,1049,1052],{},"You could click this together in the console, but the AWS Serverless Application Model (SAM) lets you declare the function, the API, and the permissions in one file, and it doubles as a local test rig. A ",[817,1050,1051],{},"template.yaml"," for our image-based function:",[822,1054,1056],{"className":882,"code":1055,"language":884,"meta":827,"style":827},"AWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\n\nResources:\n  Thumbnailer:\n    Type: AWS::Serverless::Function\n    Properties:\n      PackageType: Image\n      Architectures: [arm64]\n      MemorySize: 512\n      Timeout: 30\n      Events:\n        Resize:\n          Type: HttpApi\n          Properties:\n            Path: \u002Fthumbnail\n            Method: POST\n    Metadata:\n      Dockerfile: Dockerfile\n      DockerContext: .\n      DockerTag: latest\n\nOutputs:\n  ApiUrl:\n    Value: !Sub \"https:\u002F\u002F${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com\u002Fthumbnail\"\n",[817,1057,1058,1068,1078,1083,1090,1097,1107,1114,1124,1136,1146,1156,1163,1171,1182,1190,1201,1212,1220,1231,1242,1253,1258,1266,1274],{"__ignoreMap":827},[831,1059,1060,1063,1065],{"class":833,"line":69},[831,1061,1062],{"class":891},"AWSTemplateFormatVersion",[831,1064,896],{"class":895},[831,1066,1067],{"class":839},"\"2010-09-09\"\n",[831,1069,1070,1073,1075],{"class":833,"line":198},[831,1071,1072],{"class":891},"Transform",[831,1074,896],{"class":895},[831,1076,1077],{"class":839},"AWS::Serverless-2016-10-31\n",[831,1079,1080],{"class":833,"line":310},[831,1081,1082],{"emptyLinePlaceholder":11},"\n",[831,1084,1085,1088],{"class":833,"line":88},[831,1086,1087],{"class":891},"Resources",[831,1089,907],{"class":895},[831,1091,1092,1095],{"class":833,"line":213},[831,1093,1094],{"class":891},"  Thumbnailer",[831,1096,907],{"class":895},[831,1098,1099,1102,1104],{"class":833,"line":167},[831,1100,1101],{"class":891},"    Type",[831,1103,896],{"class":895},[831,1105,1106],{"class":839},"AWS::Serverless::Function\n",[831,1108,1109,1112],{"class":833,"line":13},[831,1110,1111],{"class":891},"    Properties",[831,1113,907],{"class":895},[831,1115,1116,1119,1121],{"class":833,"line":51},[831,1117,1118],{"class":891},"      PackageType",[831,1120,896],{"class":895},[831,1122,1123],{"class":839},"Image\n",[831,1125,1126,1129,1131,1134],{"class":833,"line":67},[831,1127,1128],{"class":891},"      Architectures",[831,1130,946],{"class":895},[831,1132,1133],{"class":839},"arm64",[831,1135,952],{"class":895},[831,1137,1138,1141,1143],{"class":833,"line":10},[831,1139,1140],{"class":891},"      MemorySize",[831,1142,896],{"class":895},[831,1144,1145],{"class":846},"512\n",[831,1147,1148,1151,1153],{"class":833,"line":136},[831,1149,1150],{"class":891},"      Timeout",[831,1152,896],{"class":895},[831,1154,1155],{"class":846},"30\n",[831,1157,1158,1161],{"class":833,"line":49},[831,1159,1160],{"class":891},"      Events",[831,1162,907],{"class":895},[831,1164,1166,1169],{"class":833,"line":1165},13,[831,1167,1168],{"class":891},"        Resize",[831,1170,907],{"class":895},[831,1172,1174,1177,1179],{"class":833,"line":1173},14,[831,1175,1176],{"class":891},"          Type",[831,1178,896],{"class":895},[831,1180,1181],{"class":839},"HttpApi\n",[831,1183,1185,1188],{"class":833,"line":1184},15,[831,1186,1187],{"class":891},"          Properties",[831,1189,907],{"class":895},[831,1191,1193,1196,1198],{"class":833,"line":1192},16,[831,1194,1195],{"class":891},"            Path",[831,1197,896],{"class":895},[831,1199,1200],{"class":839},"\u002Fthumbnail\n",[831,1202,1204,1207,1209],{"class":833,"line":1203},17,[831,1205,1206],{"class":891},"            Method",[831,1208,896],{"class":895},[831,1210,1211],{"class":839},"POST\n",[831,1213,1215,1218],{"class":833,"line":1214},18,[831,1216,1217],{"class":891},"    Metadata",[831,1219,907],{"class":895},[831,1221,1223,1226,1228],{"class":833,"line":1222},19,[831,1224,1225],{"class":891},"      Dockerfile",[831,1227,896],{"class":895},[831,1229,1230],{"class":839},"Dockerfile\n",[831,1232,1234,1237,1239],{"class":833,"line":1233},20,[831,1235,1236],{"class":891},"      DockerContext",[831,1238,896],{"class":895},[831,1240,1241],{"class":846},".\n",[831,1243,1245,1248,1250],{"class":833,"line":1244},21,[831,1246,1247],{"class":891},"      DockerTag",[831,1249,896],{"class":895},[831,1251,1252],{"class":839},"latest\n",[831,1254,1256],{"class":833,"line":1255},22,[831,1257,1082],{"emptyLinePlaceholder":11},[831,1259,1261,1264],{"class":833,"line":1260},23,[831,1262,1263],{"class":891},"Outputs",[831,1265,907],{"class":895},[831,1267,1269,1272],{"class":833,"line":1268},24,[831,1270,1271],{"class":891},"  ApiUrl",[831,1273,907],{"class":895},[831,1275,1277,1280,1282,1285],{"class":833,"line":1276},25,[831,1278,1279],{"class":891},"    Value",[831,1281,896],{"class":895},[831,1283,1284],{"class":927},"!Sub",[831,1286,1287],{"class":839}," \"https:\u002F\u002F${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com\u002Fthumbnail\"\n",[789,1289,1290,1291,1294],{},"The ",[817,1292,1293],{},"HttpApi"," event creates an HTTP API for you and (this is the quietly useful bit) wires up the permission that lets API Gateway invoke the function. Building and deploying is two commands:",[822,1296,1298],{"className":824,"code":1297,"language":826,"meta":827,"style":827},"sam build\nsam deploy --guided\n",[817,1299,1300,1308],{"__ignoreMap":827},[831,1301,1302,1305],{"class":833,"line":69},[831,1303,1304],{"class":836},"sam",[831,1306,1307],{"class":839}," build\n",[831,1309,1310,1312,1315],{"class":833,"line":198},[831,1311,1304],{"class":836},[831,1313,1314],{"class":839}," deploy",[831,1316,1317],{"class":846}," --guided\n",[789,1319,1320,1321,1324,1325,1328],{},"On the first guided deploy, SAM offers to create and manage a private ECR repository for your image automatically, so you can skip the manual ",[817,1322,1323],{},"aws ecr"," dance from part one entirely. After that, ",[817,1326,1327],{},"sam deploy"," redeploys with the settings it saved.",[799,1330,1332],{"id":1331},"handle-the-request-and-return-a-real-image","Handle the request, and return a real image",[789,1334,1335,1336,1339,1340,1343,1344,1347,1348,1351],{},"Over HTTP, the function no longer receives a bare dictionary. Payload format 2.0 wraps the request: the JSON you posted is in ",[817,1337,1338],{},"event[\"body\"]",", the method is at ",[817,1341,1342],{},"event[\"requestContext\"][\"http\"][\"method\"]",", and there are ",[817,1345,1346],{},"rawPath"," and ",[817,1349,1350],{},"queryStringParameters"," fields too. The neat trick is to make the handler work for both an HTTP request and a direct invoke:",[822,1353,1356],{"className":1354,"code":1355,"language":53,"meta":827,"style":827},"language-python shiki shiki-themes github-dark github-dark","import base64\nimport io\nimport json\nfrom urllib.request import urlopen\n\nfrom PIL import Image\n\n\ndef load_image_bytes(params):\n    if \"image_base64\" in params:\n        return base64.b64decode(params[\"image_base64\"])\n    if \"image_url\" in params:\n        with urlopen(params[\"image_url\"]) as response:\n            return response.read()\n    raise ValueError(\"Provide image_base64 or image_url\")\n\n\ndef handler(event, context):\n    over_http = \"requestContext\" in event or \"body\" in event\n\n    # An HTTP request wraps the payload in a body; a direct invoke passes it as-is.\n    if over_http:\n        body = event.get(\"body\") or \"{}\"\n        if event.get(\"isBase64Encoded\"):\n            body = base64.b64decode(body).decode()\n        params = json.loads(body)\n    else:\n        params = event\n\n    width = int(params.get(\"width\", 200))\n    image = Image.open(io.BytesIO(load_image_bytes(params)))\n    image.thumbnail((width, width))\n\n    buffer = io.BytesIO()\n    image.save(buffer, format=\"PNG\")\n    thumbnail = base64.b64encode(buffer.getvalue()).decode()\n\n    if over_http:\n        # Return the PNG itself so a browser renders it directly.\n        return {\n            \"statusCode\": 200,\n            \"headers\": {\"Content-Type\": \"image\u002Fpng\"},\n            \"isBase64Encoded\": True,\n            \"body\": thumbnail,\n        }\n\n    return {\"width\": image.width, \"height\": image.height, \"thumbnail_base64\": thumbnail}\n",[817,1357,1358,1366,1373,1380,1393,1397,1410,1414,1418,1429,1443,1457,1468,1488,1496,1513,1517,1521,1531,1558,1562,1568,1575,1602,1615,1625,1636,1644,1653,1658,1684,1695,1701,1706,1717,1734,1745,1750,1756,1762,1770,1783,1803,1816,1825,1831,1836],{"__ignoreMap":827},[831,1359,1360,1363],{"class":833,"line":69},[831,1361,1362],{"class":927},"import",[831,1364,1365],{"class":895}," base64\n",[831,1367,1368,1370],{"class":833,"line":198},[831,1369,1362],{"class":927},[831,1371,1372],{"class":895}," io\n",[831,1374,1375,1377],{"class":833,"line":310},[831,1376,1362],{"class":927},[831,1378,1379],{"class":895}," json\n",[831,1381,1382,1385,1388,1390],{"class":833,"line":88},[831,1383,1384],{"class":927},"from",[831,1386,1387],{"class":895}," urllib.request ",[831,1389,1362],{"class":927},[831,1391,1392],{"class":895}," urlopen\n",[831,1394,1395],{"class":833,"line":213},[831,1396,1082],{"emptyLinePlaceholder":11},[831,1398,1399,1401,1404,1407],{"class":833,"line":167},[831,1400,1384],{"class":927},[831,1402,1403],{"class":846}," PIL",[831,1405,1406],{"class":927}," import",[831,1408,1409],{"class":895}," Image\n",[831,1411,1412],{"class":833,"line":13},[831,1413,1082],{"emptyLinePlaceholder":11},[831,1415,1416],{"class":833,"line":51},[831,1417,1082],{"emptyLinePlaceholder":11},[831,1419,1420,1423,1426],{"class":833,"line":67},[831,1421,1422],{"class":927},"def",[831,1424,1425],{"class":836}," load_image_bytes",[831,1427,1428],{"class":895},"(params):\n",[831,1430,1431,1434,1437,1440],{"class":833,"line":10},[831,1432,1433],{"class":927},"    if",[831,1435,1436],{"class":839}," \"image_base64\"",[831,1438,1439],{"class":927}," in",[831,1441,1442],{"class":895}," params:\n",[831,1444,1445,1448,1451,1454],{"class":833,"line":136},[831,1446,1447],{"class":927},"        return",[831,1449,1450],{"class":895}," base64.b64decode(params[",[831,1452,1453],{"class":839},"\"image_base64\"",[831,1455,1456],{"class":895},"])\n",[831,1458,1459,1461,1464,1466],{"class":833,"line":49},[831,1460,1433],{"class":927},[831,1462,1463],{"class":839}," \"image_url\"",[831,1465,1439],{"class":927},[831,1467,1442],{"class":895},[831,1469,1470,1473,1476,1479,1482,1485],{"class":833,"line":1165},[831,1471,1472],{"class":927},"        with",[831,1474,1475],{"class":895}," urlopen(params[",[831,1477,1478],{"class":839},"\"image_url\"",[831,1480,1481],{"class":895},"]) ",[831,1483,1484],{"class":927},"as",[831,1486,1487],{"class":895}," response:\n",[831,1489,1490,1493],{"class":833,"line":1173},[831,1491,1492],{"class":927},"            return",[831,1494,1495],{"class":895}," response.read()\n",[831,1497,1498,1501,1504,1507,1510],{"class":833,"line":1184},[831,1499,1500],{"class":927},"    raise",[831,1502,1503],{"class":846}," ValueError",[831,1505,1506],{"class":895},"(",[831,1508,1509],{"class":839},"\"Provide image_base64 or image_url\"",[831,1511,1512],{"class":895},")\n",[831,1514,1515],{"class":833,"line":1192},[831,1516,1082],{"emptyLinePlaceholder":11},[831,1518,1519],{"class":833,"line":1203},[831,1520,1082],{"emptyLinePlaceholder":11},[831,1522,1523,1525,1528],{"class":833,"line":1214},[831,1524,1422],{"class":927},[831,1526,1527],{"class":836}," handler",[831,1529,1530],{"class":895},"(event, context):\n",[831,1532,1533,1536,1539,1542,1544,1547,1550,1553,1555],{"class":833,"line":1222},[831,1534,1535],{"class":895},"    over_http ",[831,1537,1538],{"class":927},"=",[831,1540,1541],{"class":839}," \"requestContext\"",[831,1543,1439],{"class":927},[831,1545,1546],{"class":895}," event ",[831,1548,1549],{"class":927},"or",[831,1551,1552],{"class":839}," \"body\"",[831,1554,1439],{"class":927},[831,1556,1557],{"class":895}," event\n",[831,1559,1560],{"class":833,"line":1233},[831,1561,1082],{"emptyLinePlaceholder":11},[831,1563,1564],{"class":833,"line":1244},[831,1565,1567],{"class":1566},"sJ8bj","    # An HTTP request wraps the payload in a body; a direct invoke passes it as-is.\n",[831,1569,1570,1572],{"class":833,"line":1255},[831,1571,1433],{"class":927},[831,1573,1574],{"class":895}," over_http:\n",[831,1576,1577,1580,1582,1585,1588,1591,1593,1596,1599],{"class":833,"line":1260},[831,1578,1579],{"class":895},"        body ",[831,1581,1538],{"class":927},[831,1583,1584],{"class":895}," event.get(",[831,1586,1587],{"class":839},"\"body\"",[831,1589,1590],{"class":895},") ",[831,1592,1549],{"class":927},[831,1594,1595],{"class":839}," \"",[831,1597,1598],{"class":846},"{}",[831,1600,1601],{"class":839},"\"\n",[831,1603,1604,1607,1609,1612],{"class":833,"line":1268},[831,1605,1606],{"class":927},"        if",[831,1608,1584],{"class":895},[831,1610,1611],{"class":839},"\"isBase64Encoded\"",[831,1613,1614],{"class":895},"):\n",[831,1616,1617,1620,1622],{"class":833,"line":1276},[831,1618,1619],{"class":895},"            body ",[831,1621,1538],{"class":927},[831,1623,1624],{"class":895}," base64.b64decode(body).decode()\n",[831,1626,1628,1631,1633],{"class":833,"line":1627},26,[831,1629,1630],{"class":895},"        params ",[831,1632,1538],{"class":927},[831,1634,1635],{"class":895}," json.loads(body)\n",[831,1637,1639,1642],{"class":833,"line":1638},27,[831,1640,1641],{"class":927},"    else",[831,1643,907],{"class":895},[831,1645,1647,1649,1651],{"class":833,"line":1646},28,[831,1648,1630],{"class":895},[831,1650,1538],{"class":927},[831,1652,1557],{"class":895},[831,1654,1656],{"class":833,"line":1655},29,[831,1657,1082],{"emptyLinePlaceholder":11},[831,1659,1661,1664,1666,1669,1672,1675,1678,1681],{"class":833,"line":1660},30,[831,1662,1663],{"class":895},"    width ",[831,1665,1538],{"class":927},[831,1667,1668],{"class":846}," int",[831,1670,1671],{"class":895},"(params.get(",[831,1673,1674],{"class":839},"\"width\"",[831,1676,1677],{"class":895},", ",[831,1679,1680],{"class":846},"200",[831,1682,1683],{"class":895},"))\n",[831,1685,1687,1690,1692],{"class":833,"line":1686},31,[831,1688,1689],{"class":895},"    image ",[831,1691,1538],{"class":927},[831,1693,1694],{"class":895}," Image.open(io.BytesIO(load_image_bytes(params)))\n",[831,1696,1698],{"class":833,"line":1697},32,[831,1699,1700],{"class":895},"    image.thumbnail((width, width))\n",[831,1702,1704],{"class":833,"line":1703},33,[831,1705,1082],{"emptyLinePlaceholder":11},[831,1707,1709,1712,1714],{"class":833,"line":1708},34,[831,1710,1711],{"class":895},"    buffer ",[831,1713,1538],{"class":927},[831,1715,1716],{"class":895}," io.BytesIO()\n",[831,1718,1720,1723,1727,1729,1732],{"class":833,"line":1719},35,[831,1721,1722],{"class":895},"    image.save(buffer, ",[831,1724,1726],{"class":1725},"s-3mD","format",[831,1728,1538],{"class":927},[831,1730,1731],{"class":839},"\"PNG\"",[831,1733,1512],{"class":895},[831,1735,1737,1740,1742],{"class":833,"line":1736},36,[831,1738,1739],{"class":895},"    thumbnail ",[831,1741,1538],{"class":927},[831,1743,1744],{"class":895}," base64.b64encode(buffer.getvalue()).decode()\n",[831,1746,1748],{"class":833,"line":1747},37,[831,1749,1082],{"emptyLinePlaceholder":11},[831,1751,1752,1754],{"class":833,"line":202},[831,1753,1433],{"class":927},[831,1755,1574],{"class":895},[831,1757,1759],{"class":833,"line":1758},39,[831,1760,1761],{"class":1566},"        # Return the PNG itself so a browser renders it directly.\n",[831,1763,1765,1767],{"class":833,"line":1764},40,[831,1766,1447],{"class":927},[831,1768,1769],{"class":895}," {\n",[831,1771,1773,1776,1778,1780],{"class":833,"line":1772},41,[831,1774,1775],{"class":839},"            \"statusCode\"",[831,1777,896],{"class":895},[831,1779,1680],{"class":846},[831,1781,1782],{"class":895},",\n",[831,1784,1786,1789,1792,1795,1797,1800],{"class":833,"line":1785},42,[831,1787,1788],{"class":839},"            \"headers\"",[831,1790,1791],{"class":895},": {",[831,1793,1794],{"class":839},"\"Content-Type\"",[831,1796,896],{"class":895},[831,1798,1799],{"class":839},"\"image\u002Fpng\"",[831,1801,1802],{"class":895},"},\n",[831,1804,1806,1809,1811,1814],{"class":833,"line":1805},43,[831,1807,1808],{"class":839},"            \"isBase64Encoded\"",[831,1810,896],{"class":895},[831,1812,1813],{"class":846},"True",[831,1815,1782],{"class":895},[831,1817,1819,1822],{"class":833,"line":1818},44,[831,1820,1821],{"class":839},"            \"body\"",[831,1823,1824],{"class":895},": thumbnail,\n",[831,1826,1828],{"class":833,"line":1827},45,[831,1829,1830],{"class":895},"        }\n",[831,1832,1834],{"class":833,"line":1833},46,[831,1835,1082],{"emptyLinePlaceholder":11},[831,1837,1839,1842,1845,1847,1850,1853,1856,1859],{"class":833,"line":1838},47,[831,1840,1841],{"class":927},"    return",[831,1843,1844],{"class":895}," {",[831,1846,1674],{"class":839},[831,1848,1849],{"class":895},": image.width, ",[831,1851,1852],{"class":839},"\"height\"",[831,1854,1855],{"class":895},": image.height, ",[831,1857,1858],{"class":839},"\"thumbnail_base64\"",[831,1860,1861],{"class":895},": thumbnail}\n",[789,1863,1864,1865,1867,1868,1677,1871,1677,1874,1877,1878,1881,1882,1884,1885,1888],{},"Format 2.0 gives you two ways to respond. Return any JSON-serializable value and API Gateway auto-wraps it as a ",[817,1866,1680],{}," with a JSON content type, which is great for plain data. Or return an explicit object with ",[817,1869,1870],{},"statusCode",[817,1872,1873],{},"headers",[817,1875,1876],{},"body",", and ",[817,1879,1880],{},"isBase64Encoded"," for full control. To send back an actual PNG that a browser can show, you need that explicit form: base64-encode the bytes, set ",[817,1883,1880],{}," to ",[817,1886,1887],{},"true",", and the HTTP API decodes it to binary on the way out.",[799,1890,1892],{"id":1891},"a-note-on-permissions","A note on permissions",[789,1894,1895,1896,1898],{},"With the SAM ",[817,1897,1293],{}," event above, the resource-based policy that allows API Gateway to call your function is created automatically. If you ever wire it by hand, that policy is what you are adding:",[822,1900,1902],{"className":824,"code":1901,"language":826,"meta":827,"style":827},"aws lambda add-permission \\\n  --function-name thumbnailer \\\n  --statement-id apigateway-invoke \\\n  --action lambda:InvokeFunction \\\n  --principal apigateway.amazonaws.com \\\n  --source-arn \"arn:aws:execute-api:us-east-1:111122223333:abc123\u002F*\u002FPOST\u002Fthumbnail\"\n",[817,1903,1904,1915,1923,1933,1943,1953],{"__ignoreMap":827},[831,1905,1906,1908,1910,1913],{"class":833,"line":69},[831,1907,15],{"class":836},[831,1909,840],{"class":839},[831,1911,1912],{"class":839}," add-permission",[831,1914,847],{"class":846},[831,1916,1917,1919,1921],{"class":833,"line":198},[831,1918,852],{"class":846},[831,1920,855],{"class":839},[831,1922,847],{"class":846},[831,1924,1925,1928,1931],{"class":833,"line":310},[831,1926,1927],{"class":846},"  --statement-id",[831,1929,1930],{"class":839}," apigateway-invoke",[831,1932,847],{"class":846},[831,1934,1935,1938,1941],{"class":833,"line":88},[831,1936,1937],{"class":846},"  --action",[831,1939,1940],{"class":839}," lambda:InvokeFunction",[831,1942,847],{"class":846},[831,1944,1945,1948,1951],{"class":833,"line":213},[831,1946,1947],{"class":846},"  --principal",[831,1949,1950],{"class":839}," apigateway.amazonaws.com",[831,1952,847],{"class":846},[831,1954,1955,1958],{"class":833,"line":167},[831,1956,1957],{"class":846},"  --source-arn",[831,1959,1960],{"class":839}," \"arn:aws:execute-api:us-east-1:111122223333:abc123\u002F*\u002FPOST\u002Fthumbnail\"\n",[789,1962,1963,1964,1967,1968,1971],{},"The principal is always ",[817,1965,1966],{},"apigateway.amazonaws.com"," and the action is ",[817,1969,1970],{},"lambda:InvokeFunction",". And because none of this cares how the function is packaged, a container-image Lambda is invoked by API Gateway exactly like a zip one.",[799,1973,1975],{"id":1974},"test-the-whole-api-locally","Test the whole API locally",[789,1977,1978],{},"SAM can stand up a local API Gateway in front of your container, payload format 2.0 envelope and all:",[822,1980,1982],{"className":824,"code":1981,"language":826,"meta":827,"style":827},"sam local start-api\n",[817,1983,1984],{"__ignoreMap":827},[831,1985,1986,1988,1991],{"class":833,"line":69},[831,1987,1304],{"class":836},[831,1989,1990],{"class":839}," local",[831,1992,1993],{"class":839}," start-api\n",[789,1995,1996,1997,2000,2001,2003],{},"It serves on ",[817,1998,1999],{},"http:\u002F\u002F127.0.0.1:3000"," by default. Because our handler returns the PNG with ",[817,2002,1880],{},", you can save the response straight to a file and open it:",[822,2005,2007],{"className":824,"code":2006,"language":826,"meta":827,"style":827},"curl -s -X POST http:\u002F\u002F127.0.0.1:3000\u002Fthumbnail \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"image_url\": \"https:\u002F\u002Fhttpbin.org\u002Fimage\u002Fjpeg\", \"width\": 120}' \\\n  --output thumb.png\n",[817,2008,2009,2028,2038,2048],{"__ignoreMap":827},[831,2010,2011,2014,2017,2020,2023,2026],{"class":833,"line":69},[831,2012,2013],{"class":836},"curl",[831,2015,2016],{"class":846}," -s",[831,2018,2019],{"class":846}," -X",[831,2021,2022],{"class":839}," POST",[831,2024,2025],{"class":839}," http:\u002F\u002F127.0.0.1:3000\u002Fthumbnail",[831,2027,847],{"class":846},[831,2029,2030,2033,2036],{"class":833,"line":198},[831,2031,2032],{"class":846},"  -H",[831,2034,2035],{"class":839}," \"Content-Type: application\u002Fjson\"",[831,2037,847],{"class":846},[831,2039,2040,2043,2046],{"class":833,"line":310},[831,2041,2042],{"class":846},"  -d",[831,2044,2045],{"class":839}," '{\"image_url\": \"https:\u002F\u002Fhttpbin.org\u002Fimage\u002Fjpeg\", \"width\": 120}'",[831,2047,847],{"class":846},[831,2049,2050,2053],{"class":833,"line":88},[831,2051,2052],{"class":846},"  --output",[831,2054,2055],{"class":839}," thumb.png\n",[789,2057,2058,2059,2062],{},"For a single function with no HTTP layer, ",[817,2060,2061],{},"sam local invoke -e event.json"," runs it against an event file instead. Between this and the Runtime Interface Emulator from part one, you can exercise the whole thing before anything reaches AWS.",[799,2064,2066],{"id":2065},"call-it-from-a-laravel-app","Call it from a Laravel app",[789,2068,2069,2070,2073],{},"Here is the payoff for the PHP crowd, and it mirrors the two front doors exactly. Install the SDK with ",[817,2071,2072],{},"composer require aws\u002Faws-sdk-php"," (Laravel 13 wants PHP 8.3+).",[789,2075,2076],{},"Through API Gateway it is just an HTTP request, so Laravel's HTTP client is all you need. Since the endpoint returns image bytes, you can pass them straight through to the browser:",[822,2078,2081],{"className":2079,"code":2080,"language":59,"meta":827,"style":827},"language-php shiki shiki-themes github-dark github-dark","use Illuminate\\Support\\Facades\\Http;\n\n$response = Http::post('https:\u002F\u002Fabc123.execute-api.us-east-1.amazonaws.com\u002Fthumbnail', [\n    'image_url' => 'https:\u002F\u002Fexample.com\u002Fcat.jpg',\n    'width' => 120,\n]);\n\nreturn response($response->body(), 200)->header('Content-Type', 'image\u002Fpng');\n",[817,2082,2083,2094,2098,2122,2135,2147,2152,2156],{"__ignoreMap":827},[831,2084,2085,2088,2091],{"class":833,"line":69},[831,2086,2087],{"class":927},"use",[831,2089,2090],{"class":846}," Illuminate\\Support\\Facades\\Http",[831,2092,2093],{"class":895},";\n",[831,2095,2096],{"class":833,"line":198},[831,2097,1082],{"emptyLinePlaceholder":11},[831,2099,2100,2103,2105,2108,2111,2114,2116,2119],{"class":833,"line":310},[831,2101,2102],{"class":895},"$response ",[831,2104,1538],{"class":927},[831,2106,2107],{"class":846}," Http",[831,2109,2110],{"class":927},"::",[831,2112,2113],{"class":836},"post",[831,2115,1506],{"class":895},[831,2117,2118],{"class":839},"'https:\u002F\u002Fabc123.execute-api.us-east-1.amazonaws.com\u002Fthumbnail'",[831,2120,2121],{"class":895},", [\n",[831,2123,2124,2127,2130,2133],{"class":833,"line":88},[831,2125,2126],{"class":839},"    'image_url'",[831,2128,2129],{"class":927}," =>",[831,2131,2132],{"class":839}," 'https:\u002F\u002Fexample.com\u002Fcat.jpg'",[831,2134,1782],{"class":895},[831,2136,2137,2140,2142,2145],{"class":833,"line":213},[831,2138,2139],{"class":839},"    'width'",[831,2141,2129],{"class":927},[831,2143,2144],{"class":846}," 120",[831,2146,1782],{"class":895},[831,2148,2149],{"class":833,"line":167},[831,2150,2151],{"class":895},"]);\n",[831,2153,2154],{"class":833,"line":13},[831,2155,1082],{"emptyLinePlaceholder":11},[831,2157,2158,2161,2164,2167,2170,2172,2175,2177,2180,2182,2185,2187,2190,2192,2195],{"class":833,"line":51},[831,2159,2160],{"class":927},"return",[831,2162,2163],{"class":836}," response",[831,2165,2166],{"class":895},"($response",[831,2168,2169],{"class":927},"->",[831,2171,1876],{"class":836},[831,2173,2174],{"class":895},"(), ",[831,2176,1680],{"class":846},[831,2178,2179],{"class":895},")",[831,2181,2169],{"class":927},[831,2183,2184],{"class":836},"header",[831,2186,1506],{"class":895},[831,2188,2189],{"class":839},"'Content-Type'",[831,2191,1677],{"class":895},[831,2193,2194],{"class":839},"'image\u002Fpng'",[831,2196,2197],{"class":895},");\n",[789,2199,2200],{},"With no gateway at all, call the function directly with the AWS SDK for PHP. The handler detects the direct invoke and returns plain JSON, so you decode the base64 yourself:",[822,2202,2204],{"className":2079,"code":2203,"language":59,"meta":827,"style":827},"use Aws\\Lambda\\LambdaClient;\n\n$lambda = new LambdaClient(['region' => 'us-east-1', 'version' => 'latest']);\n\n$result = $lambda->invoke([\n    'FunctionName' => 'thumbnailer',\n    'Payload' => json_encode(['image_url' => 'https:\u002F\u002Fexample.com\u002Fcat.jpg', 'width' => 120]),\n]);\n\n$data = json_decode($result->get('Payload')->getContents(), true);\n$png = base64_decode($data['thumbnail_base64']);\n",[817,2205,2206,2215,2219,2255,2259,2277,2289,2320,2324,2328,2364],{"__ignoreMap":827},[831,2207,2208,2210,2213],{"class":833,"line":69},[831,2209,2087],{"class":927},[831,2211,2212],{"class":846}," Aws\\Lambda\\LambdaClient",[831,2214,2093],{"class":895},[831,2216,2217],{"class":833,"line":198},[831,2218,1082],{"emptyLinePlaceholder":11},[831,2220,2221,2224,2226,2229,2232,2235,2238,2240,2243,2245,2248,2250,2253],{"class":833,"line":310},[831,2222,2223],{"class":895},"$lambda ",[831,2225,1538],{"class":927},[831,2227,2228],{"class":927}," new",[831,2230,2231],{"class":846}," LambdaClient",[831,2233,2234],{"class":895},"([",[831,2236,2237],{"class":839},"'region'",[831,2239,2129],{"class":927},[831,2241,2242],{"class":839}," 'us-east-1'",[831,2244,1677],{"class":895},[831,2246,2247],{"class":839},"'version'",[831,2249,2129],{"class":927},[831,2251,2252],{"class":839}," 'latest'",[831,2254,2151],{"class":895},[831,2256,2257],{"class":833,"line":88},[831,2258,1082],{"emptyLinePlaceholder":11},[831,2260,2261,2264,2266,2269,2271,2274],{"class":833,"line":213},[831,2262,2263],{"class":895},"$result ",[831,2265,1538],{"class":927},[831,2267,2268],{"class":895}," $lambda",[831,2270,2169],{"class":927},[831,2272,2273],{"class":836},"invoke",[831,2275,2276],{"class":895},"([\n",[831,2278,2279,2282,2284,2287],{"class":833,"line":167},[831,2280,2281],{"class":839},"    'FunctionName'",[831,2283,2129],{"class":927},[831,2285,2286],{"class":839}," 'thumbnailer'",[831,2288,1782],{"class":895},[831,2290,2291,2294,2296,2299,2301,2304,2306,2308,2310,2313,2315,2317],{"class":833,"line":13},[831,2292,2293],{"class":839},"    'Payload'",[831,2295,2129],{"class":927},[831,2297,2298],{"class":846}," json_encode",[831,2300,2234],{"class":895},[831,2302,2303],{"class":839},"'image_url'",[831,2305,2129],{"class":927},[831,2307,2132],{"class":839},[831,2309,1677],{"class":895},[831,2311,2312],{"class":839},"'width'",[831,2314,2129],{"class":927},[831,2316,2144],{"class":846},[831,2318,2319],{"class":895},"]),\n",[831,2321,2322],{"class":833,"line":51},[831,2323,2151],{"class":895},[831,2325,2326],{"class":833,"line":67},[831,2327,1082],{"emptyLinePlaceholder":11},[831,2329,2330,2333,2335,2338,2341,2343,2346,2348,2351,2353,2355,2358,2360,2362],{"class":833,"line":10},[831,2331,2332],{"class":895},"$data ",[831,2334,1538],{"class":927},[831,2336,2337],{"class":846}," json_decode",[831,2339,2340],{"class":895},"($result",[831,2342,2169],{"class":927},[831,2344,2345],{"class":836},"get",[831,2347,1506],{"class":895},[831,2349,2350],{"class":839},"'Payload'",[831,2352,2179],{"class":895},[831,2354,2169],{"class":927},[831,2356,2357],{"class":836},"getContents",[831,2359,2174],{"class":895},[831,2361,1887],{"class":846},[831,2363,2197],{"class":895},[831,2365,2366,2369,2371,2374,2377,2380],{"class":833,"line":136},[831,2367,2368],{"class":895},"$png ",[831,2370,1538],{"class":927},[831,2372,2373],{"class":846}," base64_decode",[831,2375,2376],{"class":895},"($data[",[831,2378,2379],{"class":839},"'thumbnail_base64'",[831,2381,2151],{"class":895},[789,2383,2384],{},"Which one to use? From a credentialed Laravel backend, the SDK invoke is direct and needs no public endpoint. When the caller is a browser, a mobile client, or a partner, put API Gateway or a function URL in front and call that. Credentials for the SDK come from the usual AWS chain: environment variables locally, and an IAM role on the server in production, so you never hard-code keys.",[789,2386,2387],{},"You now have a function reachable over HTTP, with a clear sense of which door fits which job, tested locally and wired into a real app. One function behind one endpoint covers an enormous amount of ground. The next question is what happens when the work does not fit in one function: several steps, retries, things running in parallel. That is where Step Functions come in, and in part three we will see when they are worth it and when they are overkill.",[2389,2390,2392],"note",{"label":2391},"AWS Lambda series",[789,2393,2394,2395,2397,2398],{},"Part two of a hands-on series on running containerized Lambdas.\nPrevious: ",[793,2396,149],{"href":795}," · Next: ",[793,2399,115],{"href":2400},"\u002Farticles\u002Forchestrating-lambdas-with-step-functions",[2402,2403,2404],"style",{},"html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}",{"title":827,"searchDepth":198,"depth":198,"links":2406},[2407,2408,2409,2410,2411,2412,2413,2414],{"id":801,"depth":198,"text":802},{"id":811,"depth":198,"text":812},{"id":975,"depth":198,"text":976},{"id":1044,"depth":198,"text":1045},{"id":1331,"depth":198,"text":1332},{"id":1891,"depth":198,"text":1892},{"id":1974,"depth":198,"text":1975},{"id":2065,"depth":198,"text":2066},{"id":13,"name":14,"slug":15,"hue":16},"In part one we packaged a Python thumbnailer as a container image, pushed it to ECR, and invoked it directly. Direct invocation is perfect for internal tools and event-driven work, but it needs AWS credentials and request signing on every call, so it is not how a browser, a webhook, or a third party reaches your function. For that you want a plain HTTPS endpoint. Lambda gives you two front doors, and knowing which to pick (and when you need neither) is most of the battle.","md",{},{"title":132,"description":2416},"posts\u002Fgive-your-lambda-an-http-front-door",[15,21,143,128,27],[2423,2424,2425,2426,2427],{"name":14,"slug":15},{"name":20,"slug":21},{"name":142,"slug":143},{"name":127,"slug":128},{"name":26,"slug":27},"X0q58iEcGaS1NanhOoxufI-PkMSkMPN65Wf8L3PpP2w",1781531463967]