CodeGenieApp/serverless-express(Express Adapter for AWS)のv4への更新方法


※ 当ページには【広告/PR】を含む場合があります。
2024/08/18
Serverless Frameworkを使ってApollo(v4)サーバーをAWS Lambdaと統合する
蛸壺の技術ブログ|CodeGenieApp/serverless-express(Express Adapter for AWS)のv4への更新方法

Node.jsでビルドしたExpressアプリをAWS Lambda上で動作させるための仕組みに「アダプター」があります。

Serverless FramaworkからExpress系のアダプターとして始まった
「aws-serverless-express」が、実績を積み上げ「@vendia/serverless-express」となり、さらに一旦「serverless-express」と名前が変わり、現在の「@codegenie/serverless-express」へ至ります。

参考|@codegenie/serverless-express

既にserverless-express時代に問題になっていたLambdaの新しいNodejsランタイムに対応し、ESModuleをインポートしても問題なく動作するようになりました。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

3系から4系への手動更新

現行の「@codegenie/serverless-express」(第4世代;4系)ですが、前身の「serverless-express」(第3世代;3系)の形式にも互換性があり、そのままレガシーコードも動かせはします。

ただし、4系の形式にさっと移行したほうがアドバンテージも大きいです。

ひとまずCodeGenie公式の移行の手引をまとめてみましょう。

参考|serverless-express/UPGRADE.md

Lambdaハンドラーの修正点

個人的にはかつてのAngular Universalでよく使ったLambdaハンドラーの書式です。

            
            const awsServerlessExpress = require('aws-serverless-express')
const app = require('./app')

const binaryMimeTypes = [
    'image/*'
]
const server = awsServerlessExpress.createServer(app, null, binaryMimeTypes)

exports.handler = (event, context) => {
    awsServerlessExpress.proxy(server, event, context)
}
        
これがCodeGenie(4系)になると、以下のようなLambdaハンドラに置き換えることができます。

            
            const serverlessExpress = require('@codegenie/serverless-express');
const app = require('./app');

exports.handler = serverlessExpress({ app });
        
4系にする大きなメリットは、自前で記述する必要があったbinaryMimeTypesがいらなくなり、MIMEタイプも内部で自動判断してくれるようになります。

もしもMIMEタイプのレスポンス指定を手動でカスタマイズした場合には、
serverlessExpressの引数にbinarySettingsを指定するような扱いになります。

            
            serverlessExpress({
    app,
    binarySettings: {
        isBinary: ({ headers }) => true,
        contentTypes: [],
        contentEncodings: []
    }
})
        

Lambda event/contextを受け取る場合の修正点

Lambda自体が返す
eventcontextなどのパラメータを処理させたい場合、3系の場合だと、awsServerlessExpressMiddlewareを介してアクセスさせる方法がありました。

            
            const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');
router.use(awsServerlessExpressMiddleware.eventContext());

router.get('/', (req, res) => {
    res.json({
        stage: req.apiGateway.event.requestContext.stage
    })
})
        
これが4系になると、Expressミドルウェアに変わりに、getCurrentInvokeメソッドからLambdaのeventcontextを受け取れる形式に変化しています。

            
            const { getCurrentInvoke } = require('@codegenie/serverless-express');

router.get('/', (req, res) => {
    const currentInvoke = getCurrentInvoke();
    res.json({
        stage: currentInvoke.event.requestContext.stage
    })
})
        

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

Angular Universal(旧Angular/SSR)とSeeverless Framework(v3)の組み合わせを修正

先ほどの内容変更を踏まえて、具体的なコード修正例を1つ挙げてみましょう。

3系のアダプターの場合

Angular UniversalのLambdaハンドラー部分の実装である・
lambda.jsは3系以前では以下のようなものでした。

            
            const awsServerlessExpress = require('aws-serverless-express');
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');

//👇MIMEの指定は必須
const binaryMimeTypes = [
    'application/javascript',
    'application/json',
    'application/octet-stream',
    'application/xml',
    'image/jpeg',
    'image/png',
    'image/gif',
    'text/comma-separated-values',
    'text/css',
    'text/html',
    'text/javascript',
    'text/plain',
    'text/text',
    'text/xml',
    'image/x-icon',
    'image/svg+xml',
    'application/x-font-ttf'
];

module.exports.universal = async (event, context) => {
    try {
        //👇ESModule対応でビルド済みのExpressサーバーをインポート
        const server = await import('./dist/my-app/server/server.mjs');
        const app = await server.app();

        app.use(awsServerlessExpressMiddleware.eventContext());

        const serverAws = awsServerlessExpress.createServer(app, null, binaryMimeTypes);

        if (!app) {
            console.error('Server is not initialized');
            return;
        }
        else {
            return awsServerlessExpress.proxy(serverAws, event, context, 'PROMISE').promise;
        }
    } catch (error) {
        console.error('Failed to import app:', error);
    }
};
        
この記事の執筆段階で、Angular Universalは既にAngular/SSRとなり標準ライブラリ化しています。

合同会社タコスキングダム|蛸壺の技術ブログ
【Angularの新しいSSR環境】「Angular Universal」から「@angular/ssr」へのマイグレーションガイド

Angular UniversalをAngular/SSRへ手動で対応させるポイントを紹介

これに伴い、Angular v17以降でも3系のアダプター形式を使い続けるには、旧式の「aws-serverless-express」、現行の「@codegenie/serverless-express」、そしてSSRライブラリの「@angular/ssr」の3つがデプロイ時のパッケージに同梱する必要があります。

一例としてServerless Frameworkからデプロイするには以下のような
serverless.ymlを使うことになります。

            
            service: <デプロイ先のアプリ名>
frameworkVersion: "3"

plugins:
  - serverless-apigw-binary
  - serverless-domain-manager

provider:
  name: aws
  runtime: nodejs20.x
  memorySize: 192
  timeout: 10
  stage: ${opt:stage,"dev"}
  region: ap-northeast-1
  apiGateway:
    disableDefaultEndpoint: true

package:
  patterns:
    - '!**'
    - 'dist/**'
    - 'lambda.js'
    - 'node_modules/@vendia/**'
    - 'node_modules/aws-serverless-express/**'
    - 'node_modules/@codegenie/**'
    - "node_modules/@angular/ssr"
    - 'node_modules/binary-case/**'
    - 'node_modules/type-is/**'
    - 'node_modules/media-typer/**'
    - 'node_modules/mime-types/**'
    - 'node_modules/mime-db/**'

custom:
  apigwBinary:
    types:
      - '*/*'
  customDomains:
    - rest:
        domainName: '<カスタムドメイン名>'
        basePath: ''
        stage: ${self:provider.stage}
        createRoute53Record: true

functions:
  api:
    handler: lambda.universal
    events:
      - http:
          path: /
          method: GET
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false
      - http:
          path: /{proxy+}
          method: GET
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false
        
これを使ってServerless Frameworkから

            
            $ serverless deploy --verbose
        
すればLambda側で簡単なサーバレスアプリが動作すると思います。

4系へ移行する場合

では先程の3系を例にとり、4系への巻き上げ作業を行います。

まずはLambdaハンドラーの修正です。

            
            const serverlessExpress = require('@codegenie/serverless-express');

module.exports.universal = async (event, context) => {
    try {
        const server = await import('./dist/my-app/server/server.mjs');
        const app = await server.app();

        if (!app) {
            console.error('Server is not initialized');
            return;
        } else {
            const cachedServerlessExpress = serverlessExpress({ app });
            return cachedServerlessExpress(event, context);
        }
    } catch (error) {
        console.error('Failed to import app:', error);
    }
};
        
3系と比べ、非常にシンプルな実装になりました。

これをServerless Frameworkを使ってデプロイするには、先程の
serverless.ymlpackage部分を以下のように変えます。

            
            #...中略

package:
  patterns:
    - '!**'
    - 'dist/**'
    - 'lambda.js'
    - 'node_modules/@codegenie/**'
    - "node_modules/@angular/ssr"

#...以下略
        
見てのように、4系に移行することで、過去のアダプターが不要になり、「@codegenie/serverless-express」さえあれば事足りるようになりました。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

まとめ

今回は、AWS LambdaのExpressアプリを動作させるためのアダプターで、第4世代「@codegenie/serverless-express」のコード移行のポイントを端的に紹介してみました。

「serverless-express」シリーズのアダプターも代を重ねるごとに使いやすくはなっているのですが、Serverless Frameworkがv4からいよいよ有料化してしまうので、今後はユーザー離れが加速する気がしてなりません。

そんな著者も、既にServerless Frameworkに見切りをつけ、IaCをAWS CDKへ一本化する予定です。

今後はAWS CDKの話題もこのブログ内で増やしていこうかと思いますので、興味がありましたらちょくちょくと当サイトを覗いてみてください。

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集