Deploying a Node Express app on AWS Amplify

The following are my notes from this page

The page assumes that you have already set up an app on Amplify, and that you have connected it to a GitHub repository. I did these steps at the end, so you can find my notes for them at the bottom of this page.

I created a folder for this project named node-amplify and then did the following:

The following command will prompt you for information about your project:

npm init

Install express, typescript and types:

npm install express --save
npm install typescript ts-node @types/node @types/express --save-dev

Create a file named tsconfig.json in the project folder and put this in it:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

Create a src folder in the project root.

Create index.ts in the src folder and put this in it:

// src/index.ts
import express from 'express';

const app: express.Application = express();
const port = 3000;

app.use(express.text());

app.listen(port, () => {
  console.log(`server is listening on ${port}`);
});

// Homepage
app.get('/', (req: express.Request, res: express.Response) => {
  res.status(200).send("Hello World!");
});

// GET
app.get('/get', (req: express.Request, res: express.Response) => {
  res.status(200).header("x-get-header", "get-header-value").send("get-response-from-compute");
});

//POST
app.post('/post', (req: express.Request, res: express.Response) => {
  res.status(200).header("x-post-header", "post-header-value").send(req.body.toString());
});

//PUT
app.put('/put', (req: express.Request, res: express.Response) => {
  res.status(200).header("x-put-header", "put-header-value").send(req.body.toString());
});

//PATCH
app.patch('/patch', (req: express.Request, res: express.Response) => {
  res.status(200).header("x-patch-header", "patch-header-value").send(req.body.toString());
});

// Delete
app.delete('/delete', (req: express.Request, res: express.Response) => {
  res.status(200).header("x-delete-header", "delete-header-value").send();
});

Note that the instructions say that you should set the port to 3000.

Create a public folder in the project folder.

Then create a simple text file in the public folder called hello-world.txt, put something like this in it:

Hello world!

Add a .gitignore file with this in it:

.amplify-hosting
dist
node_modules

Create file named deploy-manifest.json in the project folder, and put this in it:

{
  "version": 1,
  "framework": { "name": "express", "version": "4.18.2" },
  "imageSettings": {
    "sizes": [
      100,
      200,
      1920
    ],
    "domains": [],
    "remotePatterns": [],
    "formats": [],
    "minimumCacheTTL": 60,
    "dangerouslyAllowSVG": false
  },
  "routes": [
    {
      "path": "/_amplify/image",
      "target": {
        "kind": "ImageOptimization",
        "cacheControl": "public, max-age=3600, immutable"
      }
    },
    {
      "path": "/*.*",
      "target": {
        "kind": "Static",
        "cacheControl": "public, max-age=2"
      },
      "fallback": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs18.x",
      "entrypoint": "index.js"
    }
  ]
}

Here are the notes on this file (directly from the source): The manifest describes how Amplify Hosting should handle the deployment of your application. The primary settings are the following.

version – Indicates the version of the deployment specification that you're using.

framework – Adjust this to specify your Express server setup.

imageSettings – This section is optional for an Express server unless you're handling image optimization.

routes – These are critical for directing traffic to the right parts of your app. The "kind": "Compute" route directs traffic to your server logic. So, I believe that requests for files (paths with a dot in them) will redirect to static files in the public folder. And route paths that don't have dots will be handled by the code in index.ts.

computeResources – Use this section to specify your Express server's runtime and entry point.

I did also notice that the entry point setting is for index.js, but the actual file is index.ts. Remember that the typescript code will be compiled to js code when we run the build.

Create a postbuild script

If you have a postbuild entry in the scripts section of the package.json file, then it will be triggered after the build script finishes running.

Create a bin directory.

Inside the bin dir, create a file named postbuild.sh and put this in it:

#!/bin/bash

rm -rf ./.amplify-hosting

mkdir -p ./.amplify-hosting/compute

cp -r ./dist ./.amplify-hosting/compute/default
cp -r ./node_modules ./.amplify-hosting/compute/default/node_modules

cp -r public ./.amplify-hosting/static

cp deploy-manifest.json ./.amplify-hosting/deploy-manifest.json

So this script will create the folders and files for deploying to Amplify when the build script completes.

To configure the postbuild script, make sure the scripts in package.json nowlook like this:

"scripts": {
  "start": "ts-node src/index.ts",
  "build": "tsc",
  "serve": "node dist/index.js",
  "postbuild": "chmod +x bin/postbuild.sh && ./bin/postbuild.sh"
}

To build the app:

npm run build

I think running the build will eventually trigger AWS Amplify to get the latest updates from your git repo.

At this point, the instructions say to push your project to github

And update your build settings for the app on Amplify, to look like this:

version: 1
frontend:
  phases:
    preBuild:
      commands:
        - nvm use 18
        - npm install
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: .amplify-hosting
    files:
      - '**/*'

At this point I had not yet created the app on Amplify, and I'm not sure if this step is required anyway, because when I did set things up in Amplify, it looked like this build file was exactly the same as the default one that was generated when I created the app.

As a side note, later I saw a note in AWS Amplify that said you could alternatively download the above build settings and put them in a file named amplify.yml in your project. Which I did. I this will trigger the deploy process whenever you run npm run build

Connecting the app to Amplify

  1. In Amplify, choose New app then Host web app
  2. Choose Github then click Continue You'll then be prompted to authorize Amplify to access your github account.
  3. Select your repo, and you the branch (main/master), then click Next
  4. Enter a name for your app, then click Next
  5. Not sure if it's necessary, but you might have to click on Build settings for the app (from the left nav) and then update the settings based on the above code sample.
  6. Click Save and deploy

Now, after you build the app (npm run build), and push to master/main, the app should automatically be redeployed to Amplify.