Sometimes we need to make calls to some RESTful APIs from an AWS Lamda function. Let’s say we use Node.js as our platform. On the surface, there are two ways to do it:
1. Use Node.js low-level http
module’s HTTP client functionality. The problem is that the low-level API is cumbersome to use, especially if sending and receiving request/response payloads is involved. You have to read the chunks, assemble the buffers, react to events, etc.
2. Use a 3rd party utility module, such as request
, which is a fine solution, except it means that you have to create a full blown AWS Lambda deployment package zip, upload it to S3, etc. If we are talking about a tiny Lambda function, it would be preferable to be able to define it as an inline function without building and uploading any packages.
There is one other solution, however. The runtime environment for AWS Lambda’s always includes the AWS SDK module and since many of the AWS services expose this or that kind of RESTful APIs, the module includes everything one needs to make high level HTTP calls to anything, not necessarily an AWS service. The only problem is that this low level functionality included in the AWS SDK is not very well documented. Luckily, it’s all JavaScript, which mean we have access to all the sources. A little bit of digging around and you can see how it includes a lot of very high level and at the same time generic functionality. To be able to call our own RESTful API we need to define it as a “service”. Then, we define our API endpoints on the service as “operations”, after which we can call those operations on our service as methods, just like any authentic AWS service provided in the SDK. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
'use strict'; // load AWS SDK module, which is always included in the runtime environment const AWS = require('aws-sdk'); // define our target API as a "service" const svc = new AWS.Service({ // the API base URL endpoint: 'https://service.example.com/api/v2', // don't parse API responses // (this is optional if you want to define shapes of all your endpoint responses) convertResponseTypes: false, // and now, our API endpoints apiConfig: { metadata: { protocol: 'rest-json' // we assume our API is JSON-based }, operations: { // API authentication endpoint Authenticate: { http: { method: 'GET', requestUri: '/login' }, input: { type: 'structure', required: [ 'login', 'password' ], members: { 'login': { // include it as a query string parameter in the call URL location: 'querystring', // parameter name locationName: 'loginName' }, 'password': { location: 'querystring', locationName: 'password', // don't show it in the logs sensitive: true } } }, // get the authentication token from the response output: { type: 'structure', members: { 'authToken': { // the token is returned as an HTTP response header location: 'header', // the header name locationName: 'AuthToken' } } } }, // get a record by id GetAccount: { http: { method: 'GET', // note the placeholder in the URI requestUri: '/accounts/{accountId}' }, input: { type: 'structure', required: [ 'auth', 'accountId' ], members: { 'auth': { // send authentication header in the HTTP request header location: 'header', locationName: 'Authorization', sensitive: true }, 'accountId': { // all kinds of validators are available type: 'integer', // include it in the call URI location: 'uri', // this is the name of the placeholder in the URI locationName: 'accountId' } } } }, // example how to send JSON data in the request body CreateAccount: { http: { method: 'POST', requestUri: '/accounts' }, input: { type: 'structure', required: [ 'auth', 'data' ], // use "data" input for the request payload payload: 'data', members: { 'auth': { location: 'header', locationName: 'Authorization', sensitive: true }, 'data': { type: 'structure', required: [ 'firstName', 'lastName' ], // the shape of the body object members: { 'firstName': {}, 'lastName': {} } } } } } } } }); // disable AWS region related login in the SDK svc.isGlobalEndpoint = true; // and now we can call our target API! exports.handler = function(event, context, callback) { // note how the methods on our service are defined after the operations svc.authenticate({ login: 'admin@example.com', password: 'SecretPassword!' }, (err, data) => { if (err) { console.error('>>> login error:', err); return callback(err); } svc.createAccount({ auth: `Bearer ${data.authToken}`, data: { firstName: 'John', lastName: 'Silver' } }, (err, data) => { if (err) { console.error('>>> operation error:', err); return callback(err); } console.log('new record:', data); callback(); }); }); }; |
There is plenty of functionality in the AWS SDK module. You can start learning it from looking at the sources in the aws-sdk
module (lib/protocol/rest_json.js
, lib/model/shape.js
, lib/service.js
and others), but it is quite easy to understand and you will no longer need to look into the source code very soon. I can say now that AWS SDK is now my HTTP client library of choice for AWS Lambda functions (especially those are not part of a larger package)!
1 thought on “Calling RESTful APIs from inline AWS Lambda functions”
Comments are closed.