カテゴリー
 CodeGenieApp/serverless-express(Express Adapter for AWS)のv4への更新方法  
※ 当ページには【広告/PR】を含む場合があります。
2024/08/18

Node.jsでビルドしたExpressアプリをAWS Lambda上で動作させるための仕組みに「アダプター」があります。
Serverless FramaworkからExpress系のアダプターとして始まった
既にserverless-express時代に問題になっていたLambdaの新しいNodejsランタイムに対応し、ESModuleをインポートしても問題なく動作するようになりました。
3系から4系への手動更新
現行の
ただし、4系の形式にさっと移行したほうがアドバンテージも大きいです。
ひとまずCodeGenie公式の移行の手引をまとめてみましょう。
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タイプのレスポンス指定を手動でカスタマイズした場合には、
serverlessExpressbinarySettings            serverlessExpress({
    app,
    binarySettings: {
        isBinary: ({ headers }) => true,
        contentTypes: [],
        contentEncodings: []
    }
})
        Lambda event/contextを受け取る場合の修正点
Lambda自体が返す
eventcontextawsServerlessExpressMiddleware            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ミドルウェアに変わりに、
getCurrentInvokeeventcontext            const { getCurrentInvoke } = require('@codegenie/serverless-express');
router.get('/', (req, res) => {
    const currentInvoke = getCurrentInvoke();
    res.json({
        stage: currentInvoke.event.requestContext.stage
    })
})
        Angular Universal(旧Angular/SSR)とSeeverless Framework(v3)の組み合わせを修正
先ほどの内容変更を踏まえて、具体的なコード修正例を1つ挙げてみましょう。
3系のアダプターの場合
Angular UniversalのLambdaハンドラー部分の実装である・
lambda.js            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 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 LambdaのExpressアプリを動作させるためのアダプターで、第4世代「@codegenie/serverless-express」のコード移行のポイントを端的に紹介してみました。
「serverless-express」シリーズのアダプターも代を重ねるごとに使いやすくはなっているのですが、Serverless Frameworkがv4からいよいよ有料化してしまうので、今後はユーザー離れが加速する気がしてなりません。
そんな著者も、既にServerless Frameworkに見切りをつけ、IaCをAWS CDKへ一本化する予定です。
今後はAWS CDKの話題もこのブログ内で増やしていこうかと思いますので、興味がありましたらちょくちょくと当サイトを覗いてみてください。