AWS API Gateway Binary support using Lambda: Real world example image resize

With the introduction of binary support in API Gateway (APIG) you can now send, for example, image binary blobs through API gateway.  I wanted to have the src attribute on an HTML img tag be a URI to an API gateway endpoint, backed by AWS Lambda.  I was unable to find clear documentation on how to do this.  The walkthrough blog post by AWS is a good start, however it adds un-necessary complexity.

Here is my attempt to make it crystal clear, using a sample image resize lambda.

Step 1: Setup API Gateway

Create a new API Gateway instance with a single GET resource.  Choose Lambda Function integration type and Use Lambda Proxy integration:
Screen Shot 2017-10-17 at 3.39.30 PM

A Lambda Proxy integration takes all the incoming HTTP request data (headers, query string, body etc) and transforms them into a lambda event object which it then reverse proxies to your Lambda function.

New Lambda Request/Response Interface

As you can imagine when your Lambda code gets control, the event object shape is different.  You can see the full input format here, however here is a snippet:

  "resource": "Resource path",
  "path": "Path parameter",
  "httpMethod": "Incoming request's method name"
  "headers": {Incoming request headers}
  "queryStringParameters": {query string parameters }
  "pathParameters": {path parameters}
  "stageVariables": {Applicable stage variables}
  "requestContext": {Request context, including authorizer-returned key-value pairs}
  "body": "A JSON string of the request payload."
  <span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"

In this post, the only important attribute is queryStringParameters

You are then also responsible for responding with a specifically shaped JSON object from your Lambda that tells the APIG how to translate to an HTTP response.

The output format docs can be found here, however here is a snippet:

  "isBase64Encoded": true|false,
  "statusCode": httpStatusCode,
  "headers": { 'Content-Type': 'image/png', ... },
  "body": "..."

Important things here are:

  • isBase64Encoded if true your body must be a base64 encoded string.  I’m always sending image data, so I hardcode this to true
  • statusCode I’m going to use 200,400 and 401

Enable Binary Support

You need to tell APIG what incoming Accept headers will warrant a binary response.  In my case, my service is an image resizer that I want to use on a src attribute.  This comes from a browser, so I have no control over what your browser sends as an Accept header.  Therefore I can tell APIG that every content type should be treated as a binary response.  I do this by specifying */* in the Binary Support section of my APIG instance:
Screen Shot 2017-10-17 at 3.58.35 PM

If you know the exact Accept header the client will send (ex: server-2-server cURL) then you can hard code these.  A simple example of serving PDFs would be application/pdf​ (a simple PDF binary lambda can be found here).

Hit save and make sure to deploy your APIG to a new stage.

Step 2: Setup Lambda function

The code below is pretty straightforward, but it essentially takes an image url (u query string param) and resizes it to a new width (w query string param).  It resizes the image on disk, then returns the binary data base64 encoded (for APIG).  I also add a super simple API key (k) check.

You will then use the APIG endpoint like this in your HTML

<img src=""><span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>


The Lambda code

'use strict';
const im = require('imagemagick');
const fs = require('fs');
const https = require('https');
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const resize = (srcPath, width, callback) => {
const dstPath = `/tmp/resized.png`,
resizeOpts = {
width: width,
srcPath: srcPath,
dstPath: dstPath
try {
im.resize(resizeOpts, (err) => {
if (err) {
throw err;
} else {
callback(null, fs.readFileSync(dstPath).toString('base64'));
} catch (err) {
console.log('Resize operation failed:', err);
const handleError = (msg, callback, code = 400) => {
const d = {
body: msg || "Unknown error",
statusCode: code
callback(null, d);
const download = (srcUrl, dest) => {
return new Promise((resolve, reject) => {
let urlParts = {};
try {
urlParts = url.parse(srcUrl);
} catch (e) {
let file = fs.createWriteStream(dest);
const protocolLib = ('https:' === urlParts.protocol)
? https
: http;
let request = protocolLib.get(srcUrl, (response) => {
if (response.statusCode !== 200) {
reject(new Error('Non 200 status ' + response.statusCode));
file.on('finish', () => {
file.close(resolve); // close() is async, call cb after close completes.
// check for request error too
request.on('error', (err) => {
file.on('error', (err) => { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
exports.handler = (event, context, callback) => {
const qs = event.queryStringParameters;
if ('myKey' !== qs.k) {
return handleError('Not authd', callback, 401);
} else if (!qs.w) {
return handleError('Missing width (w)', callback);
} else if (!qs.u) {
return handleError('Missing URL (u)', callback);
const srcDownloadPath = '/tmp/srcImg';
download(querystring.unescape(qs.u), srcDownloadPath).then(() => {
resize(srcDownloadPath, qs.w, (err, base64ImageResized) => {
callback(null, {
isBase64Encoded: true,
statusCode: 200,
headers: {
'Content-Type': 'image/png'
body: base64ImageResized
}).catch((err) => {

view raw
hosted with ❤ by GitHub

This code could be cleaner, especially if Lambda supported non LTS NodeJs versions (>7.4  has async/await), but that is a fight for a different day.  Also the fs.unlink() are not needed, since I’m only supporting PNG and I always use the same file path.


Hopefully this helps save someone some time.  I don’t check the comments on my blog, so hit me up on twitter if you found this useful.

1 thought on “AWS API Gateway Binary support using Lambda: Real world example image resize

  1. “Hit save and make sure to deploy your APIG to a new stage.”

    After fifty different AWS blogs, stackoverflow questions, and other overlappingly interesting, but otherwise equally ineffective resources, it was not until reading that statement that the “oh, DUH!” lightbulb came on to remind me that I need to create a new deployment for APIGateway whenever the configuration changes. I had been making changes to the Binary Media Types with no visible result… and it was because I hadn’t redeployed the API – ARGH! Thanks for the thorough write-up!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close