Serve images with AWS Lambda and Node.js

A random image generator using Unsplash API

Alena Khineika
DailyJS

--

For a side project I am currently working on I needed a simple random image generator. I was recommended to take a look at unsplash.com/random, and it was exactly what I wanted!

But, it worked not exactly how I expected.

It opens images in the browser perfectly fine, but the Unsplash random image API returns a JSON object with lots of extra information about a resource, instead of a binary blob.

The example below is not the complete JSON object from a response. It is a part that includes URLs to different sizes of the image.

I can request one of these images and then format it to a binary response. So I decided to create a Lambda REST API Serverless application that fetches a resource from Unsplash and returns a binary representation of the image instead of a JSON object.

Here you can learn from my experience on how to configure a Lambda function that returns a jpeg image accessible via static URL provided by Amazon API Gateway.

What is AWS Lambda?

AWS Lambda is a service that lets you run code without managing your own server. It uses the Lambda standard runtime environment and works with the resources that Lambda provides. You also get out-of-the-box such features as deployment, maintenance, automatic scaling, code monitoring, and logging. All you need to do is supply your code in one of the languages that Lambda supports, and Lambda will run your code according to a schedule or in response to some events, e.g. it can run your code in response to HTTP requests using API Gateway.

Create a Lambda function

I assume that you already have Node installed and created Unsplash and AWS accounts, which you will need to proceed with this tutorial. AWS Free Tier is a great tool to learn and experiment with AWS functionality.

Note, that during registration, you will have to provide your payment card details, but you can set the budget limit in setting, so you won’t accidentally start getting bills for overuse of resources.

To get started with Lambda, use the Lambda console to create a new function. Click the `Create function` button that will bring you to a basic setup page where you can specify a function name and choose the language to use to write your function.

I selected the `Author from scratch` option and `Node.js 14.x` as my desirable runtime.

I also gave the `GetRandomImage` name to my function.

Lambda automatically creates default code for the function, which allows you to check out the expected format of the handler.

You can write Lambda functions directly in the AWS code editor but if your function depends on external modules or going to significantly grow in size, you should precompile code together with dependencies on your local machine and upload it as a .zip file to AWS Lambda.

Create a project on localhost

Run npm init and create a file named index.js in your project root directory. The source code of the app is pretty simple and small therefore I leave it without explanation here. Feel free to reach out in comments if you have any questions.

The only thing I want to draw your attention to is a part where I prepare the response in a specific format compatible with AWS API Gateway in order to serve images.

I use Axios to download the binary file from the URL, then I convert it to base64 string and tell my Lambda function to return statusCode 200, base64 body, and isBase64Encoded set to true.

To run it locally create the .env file in the project root directory and set UNSPLASH_ACCESS_KEY that you can find it in your Unsplash user settings. Without the access key, you will get the `Request failed with status code 401` error that tells you that the user is unauthorized to request the resource. Then install project dependencies and call the handler:

If your result looks like the gist below, then you are ready to deploy your function to AWS.

Deploy to AWS

We are going to proceed with the `Upload from .zip file` option since our project has npm dependencies. Let’s create a .zip file and upload it to AWS.

Note, that your local version of Node should match the selected Lambda runtime, because you upload precompiled dependencies.

It deploys your function to AWS and loads code to the virtual editor where you can modify it and deploy changes with a single button click.

Note, that if you need to use additional npm dependencies you will have to install them locally, create a new .zip file and upload it again in order to add these precompiled dependencies to AWS.

If you changed the function a lot and your .zip file became bigger than 10 MB, use Amazon S3 to store the file and use URL to the file to upload it from `Amazon S3 location`.

Note, that the total unzipped size of the function and all layers can’t exceed the unzipped deployment package size limit of 250 MB.

Don’t forget to add environment variables to the Lambda configuration section. The production setup does not include the .env file where you locally specified the unsplash access key.

Note, that all access keys, ids, and custom URLs from this tutorial are mocks and they won’t work for you. You should use your own values valid for your Unsplash and AWS accounts.

Change the amount of time that Lambda allows a function to run before stopping it. The default is 3 seconds, make it 20 seconds to give the function enough time to download the image and format the response.

Manually invoke your Lambda function using the sample event data provided in the console. Since our function does not rely on any input parameters, these values won’t affect the function execution.

The green box tells you that the execution succeeded and if you expand the box you will see the exact response we had on localhost.

Last but not least…

Amazon API Gateway configuration

You can create a REST API endpoint for your Lambda function by using Amazon API Gateway, that provides tools for creating and documenting web APIs and can route requests to Lambda functions. The Lambda runtime serializes the response object into JSON and sends it to the API. The API parses the response, uses it to create an HTTP response, and sends it to the client that made the original request.

I have to admit that without previous experience it was pretty tricky to understand what is the right combination of the Lambda function response format and API Gateway settings to return a valid jpeg response by URL.

You can refer to the official documentation because it provides a more detailed explanation of configuration options. I am going to repeat some parts from it here, but I also want to include more pictures and show you a setup that worked for me.

Open the API Gateway console.

Click `Create API` and select build `REST API` project to get control over the requests and responses of your Lambda project.

If this is your first time using API Gateway, you will see a page that introduces you to the features of the service. As part of the educational process, you will create a demo `Pet Store` endpoint using a sample REST API.

When you are done with the demo project you will be able to create an empty API as follows:

Choose API name, e.g. `LambdaSimpleProxy` and leave `Endpoint type` set to `Regional`. After creating a new API, click on the root resource (/) in the `Resources` tree and from the `Actions` dropdown menu select `Create Resource` item.

Give the name to your resource, for example `photos-random`. Leave `Configure as proxy resource` and `Enable API Gateway CORS` unchecked.

To set up the GET method, on the resources list, click `/photos-random` and from the `Actions` menu, choose `Create method`. Choose GET from the dropdown menu, and click the checkmark icon.

Leave the `Integration type` set to `Lambda Function` and choose `Use Lambda Proxy integration`. In the `Lambda Function` field, type any character and choose `GetRandomImage` from the dropdown menu. Leave `Use Default Timeout` checked and save the method.

After changes are saved click on the GET method and this will open the method execution scheme.

Click on the `Method Response` title and it will open the page with information about the method’s response types, their headers, and content types. Set `Content-Type` as `image/jpeg` in the Response Header for HTTP Status 200.

If you now deploy your REST API you will get the following response in the browser:

We send the image as a base64 string, but API Gateway should be able to convert it from base64 to binary.

Go to API settings and configure binary support for your API by specifying which media types should be treated as binary types. API Gateway will look at the Content-Type and Accept HTTP headers to decide how to handle the body. Use */* to enable all media types.

Open CloudShell that is a browser-based shell with AWS CLI access from the AWS Management Console. You can call the same commands from your local terminal, but for this, you will need AWS CLI installed and configured.

To return a binary blob instead of a base64-encoded payload from the endpoint, we should set the `contentHandling` property of the `IntegrationResponse` resource to `CONVERT_TO_BINARY`. To do this, submit a PATCH request, as follows:

You can find your `rest-api-id` and `resource-id` values at the resource header.

Don’t forget to deploy your REST API.

After completion, you will see a stage editor page with the invoke URL (you can also find this URL on Dashboard).

The invoke URL together with the resource name is the final URL that serves the image as a binary file.

Summary

To summarise I want to list all the stages you have to complete in order to serve images with Lambda:

  • Create a new Lambda function.
  • Write the Lambda handler in the virtual editor or on your local machine and deploy code to AWS.
  • Add environment variables to the Lambda configuration settings.
  • Change the amount of time that Lambda allows a function to run before stopping it. The default is 3 seconds, make it 20 seconds to give the function enough time to download the image and format the response.
  • Create Amazon REST API Gateway.
  • Add a new resource and the GET method to API.
  • Bind the method to the Lambda function.
  • Change the method response to `image/jpeg`.
  • Add */* binary media types to your API settings.
  • Use AWS Shell to set the `contentHandling` property of the `IntegrationResponse` resource to `CONVERT_TO_BINARY`.
  • Deploy REST API.

The Lambda function from this tutorial lives on Git and here are some resources that were useful for me:

AWS Documentation: What is AWS Lambda?

AWS Tutorial: Build a Hello World REST API with Lambda proxy integration

AWS Documentation: Enabling binary support using the API Gateway REST API

How to Send an Image as a Response via AWS Lambda and API Gateway? by Adil Ilhan

--

--