An AWS API Gateway proxy resource can be configured to respond to requests using a proxy integration to invoke a Lambda function.

From Set up a Proxy Integration with a Proxy Resource:

With this integration type, API Gateway applies a default mapping template to send the entire request to the Lambda function and transforms the output from the Lambda function to HTTP responses.

This makes it trivial to implement a redirection function for custom short URLs.

Example

This example matches the incoming request URL to a post ID and redirects to the corresponding post URL if found (otherwise a status of 404 is returned). It is based on the code that handles the /b short URLs for this blog (ex. https://atj.me/b904).

Depending on your requirements, the function could get the corresponding URL any number of ways (calculate it, look it up in a database, etc). I elected to load key/value pairs from a JSON file that is easily generated with Hugo (which is used to build this blog).

public/index.json

1
2
3
4
5
{
    "903": "/2017/10/bundle-lambda-functions-using-webpack/",
    "902": "/2017/10/ship-access-logs-to-cloudwatch/",
    "901": "/2017/10/generate-yearly-and-monthly-archive-pages-with-hugo-sections/"
}

ShortUrl.js

 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
const postUrls = require('public/index.json');

const blogUrl = 'https://blog.atj.me';

function redirect(url)
{
    console.log('Redirect', url);
    return {
        statusCode: 301,
        headers: {
            'Location': url
        }
    };
}

function handler(event, context, callback) {

    // Remove leading slash
    const needle = event.path.substr(1);

    // Log requested short URL
    console.log('ShortUrl', `"${needle}"`);

    // Check for /b patterns (ex. /b901 and /b/901)
    const blogIdMatches = needle.match(/^b\/?(\d+)$/i);

    // If the request URL matches the pattern and the ID exists in postUrls,
    //  redirect to post URL
    if(blogIdMatches && (blogIdMatches[1] in postUrls)) {
        return callback(null, redirect(blogUrl + postUrls[blogIdMatches[1]]));
    }

    // 404
    const body = 'Not found';
    console.log(body);
    callback(null, {statusCode: 404, body});
}

module.exports = handler;

Hugo

To create a JSON file containing a key/value pair for each post in Hugo:

  1. Enable the JSON output in config.toml
[outputs]
    home = [ "HTML", "RSS", "JSON"]
  1. Create a layouts/index.json file that outputs a row for each page in the posts section that has id defined in the frontmatter.
1
2
3
4
5
6
    {
      {{- range $index, $page := (where (where .Data.Pages "Section" "posts") ".Params.id" "!=" nil) -}}
        {{- if (gt $index 0) -}},{{- end }}
        "{{ $page.Params.id }}": "{{ $page.Permalink | relURL }}"
      {{- end }}
    }

This causes public/index.json to be generated which can be bundled with the Lambda function code using webpack.