Typescript and the Beanstalk

Typescript and the Beanstalk

Deploying typescript apps to Beanstalk with CircleCI

Before we get started, note that this post assumes that you have your CircleCI/Beanstalk integration working already. The reason for this is that setting that up itself is a very long-winded process. I may make a video about it some time, but in the mean time there is a very good medium post that explains how to set that up.

Also, a big special thanks to Rokt33r for the example repo that I shamelessly copied everything from 🙂

Why is this so different?

So, you may have seen examples of deploying node apps to Beanstalk and even got them working, but now you want to deploy a typescript app. You may be wondering how to ship your built files, since just shipping your source code isn’t enough to get it running, unlike plain node.js.

So the key thing to remember is that you deploy your built assets, not your source code. That’s actually true for even plain node apps, but it just so happens that in that case the build and the source is the same.

So, how do you do this? Well, assuming you’ve done the ~~impossible~~ hard bit of setting up AWS, Beanstalk, and CircleCI, the rest is actually quite easy.

Step 1: tsconfig.json

You should have that set up to compile your code. If you already have it, you don’t need to change it, just take a note of what outDir is as you’ll need it. If you don’t have it, or want to see an example, here is one.

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "outDir": "dist",
        "sourceMap": true,
        "baseUrl": ".",
        "paths": {
            "*": [
                "node_modules/*",
                "src/types/*"
            ]
        }
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules"
    ]
}

Step 2: package.json

You should already have start and build scripts, but if not, here is an example:

 "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },

build is the command used to build your software; in this case it’s tsc to compile the typescript down to javascript.

start is how you start your software, obviously this depends on your app but it’s usually something like node dist/index.js. Note that you’re pointing to your compiled js code. The location may not be dist; but it’s whatever you have set as outDir in tsconfig.json.

Step 3: dist step

If you’ve seen the UI for Elastic Beanstalk for AWS, you basically deploy apps to it by uploading .zip files. If you use the command line (eb deploy), it does that for you. What we essentially want to do is to build your distribution .zip file to upload. How? Essentially by zipping your dist directory.

Make a file called scripts/dist.sh, and put the following code in:

# If the directory, `dist`, doesn't exist, create `dist`
stat dist || mkdir dist
# Archive artifacts
zip dist/$npm_package_name.zip -r dist package.json package-lock.json

If you’re not using dist as the directory name for your build (remember the outDir from earlier), you need to change it above.

This will basically build your zip for you!
You can test it out manually:

errietta@Moltres [4] (git)-[master] ~/hyperbudget-backend % bash scripts/dist.sh
  File: 'dist'
  Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d      Inode: 16393011    Links: 6
Access: (0775/drwxrwxr-x)  Uid: ( 1000/errietta)   Gid: ( 1000/errietta)
Access: 2018-06-16 11:41:27.842696048 +0100
Modify: 2018-05-24 19:59:55.741014000 +0100
Change: 2018-05-24 19:59:55.741014000 +0100
 Birth: -
  adding: dist/ (stored 0%)
  adding: dist/app.js (deflated 70%)
  ....
  adding: dist/index.js (deflated 39%)
  adding: dist/index.js.map (deflated 58%)
  adding: package.json (deflated 65%)
  adding: package-lock.json (deflated 77%)
errietta@Moltres [4] (git)-[master] ~/hyperbudget-backend %

This will actually generate dist/.zip – that’s because when you run it manually, $npm_package_name is not set. Feel free to look at that zip file and verify that all your built javascript is in there. If it’s correct, delete the file, we’ll tell npm to generate it instead in the next step!

Step 4: Set up the npm dist script

Really simple, just add another script to your package.json. For example:

  "scripts": {
    "build": "tsc",
    "dist": "sh ./scripts/dist.sh",
    "start": "node dist/index.js"
  },

Now you can run your dist step by doing npm run dist! If you run that, you will now have a zip file in your dist folder that is actually named after your app, and you can open it and again verify that you have all your compiled javascript there.

Step 5: Ammend CircleCI configuration

Now we need to tell CircleCI to run npm run build and npm run dist. This will depend on your configuration, but basically edit .circleci/config.yml and make sure that npm run build and npm run dist is ran before eb deploy. For example, I have the following:

      - deploy:
          name: Deploy to Elastic Beanstalk
          command: |
            npm install && npm run build && npm run dist && eb deploy --staged MyApp-env

This will install all my dependencies, compile my app down to javascript, run npm run dist to generate the zip file, and deploy to Beanstalk. Hang on, though, how do we tell Beanstalk to use that zip file?

Step 6: Beanstalk configuration

Create or ammend your .elasticbeanstalk/config.yml file. In there, you need to add the following:

deploy:
  artifact: dist/YOUR-APP-NAME.zip

The name of the zip should be the same as your npm package name if you have set up the dist.sh script to use $npm_package_name. You can always manually run npm run dist and then see the name of the file it generates.

If you don’t have an elasticbeanstalk/config.yml file, here is a simple one that works for me:

branch-defaults:
  master:
    environment: MyApp-env
  staging:
    environment: MyApp-env
  dev:
    environment: MyApp-env
global:
  application_name: my-app
  default_platform: 64bit Amazon Linux 2017.03 v4.5 running Node 8.10
  default_region: eu-central-1
deploy:
  artifact: dist/my-app.zip

You should change your branch names and environnment names to match your deployed branches and your Beanstalk environment names respectively, and you can change the region and platform if you ned to. The important thing is for the artifact to be correct, this will tell beanstalk to deploy your zip file

That’s it!

If you set up all this correctly, circleci should now be deploying your zip file to beanstalk. You can check the CircleCI build information and verify that it runs tsc, zips up your files, and deploys to Beanstalk. Here’s what mine looks like for comparison:

#!/bin/bash -eo pipefail
npm install && npm run build && npm run dist && eb deploy --staged MyApp-env


> ursa@0.9.4 install /home/circleci/app/node_modules/ursa
> node-gyp rebuild

make: Entering directory '/home/circleci/app/node_modules/ursa/build'
  CXX(target) Release/obj.target/ursaNative/src/ursaNative.o
  SOLINK_MODULE(target) Release/obj.target/ursaNative.node
  COPY Release/ursaNative.node
make: Leaving directory '/home/circleci/app/node_modules/ursa/build'
added 232 packages in 11.433s

> my-app@1.0.0 build /home/circleci/app
> tsc


> my-app@1.0.0 dist /home/circleci/app
> sh ./scripts/dist.sh

  File: ‘dist’
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: 100016h/1048598d    Inode: 7272        Links: 4
Access: (0755/drwxr-xr-x)  Uid: ( 3434/circleci)   Gid: ( 3434/circleci)
Access: 2018-06-14 13:37:17.348967660 +0000
Modify: 2018-06-14 13:37:17.376967104 +0000
Change: 2018-06-14 13:37:17.376967104 +0000
 Birth: -
  adding: dist/ (stored 0%)
  adding: dist/App.js (deflated 51%)
  adding: dist/index.js (deflated 39%)
  adding: dist/script/ (stored 0%)
  adding: dist/App.js.map (deflated 64%)
  adding: package.json (deflated 56%)
  adding: package-lock.json (deflated 77%)
Uploading my-app/app-******.zip to S3. This may take a while.
Upload Complete.

INFO: Environment update is starting.
INFO: Deploying new version to instance(s).
INFO: New application version was deployed to running EC2 instances.
INFO: Environment update completed successfully.

You can see that it ran npm install, tsc, and then my ./scripts/dist.sh which zipped all my built files, and then successfully deployed it to EC2.

I hope this helps someone else, thanks for reading 🙂

3 Comments

  1. This has really helped me a lot. A newbie to EB, and I was having all sorts of issues trying to figure out why our TS app would deploy. Thanks much!

  2. Thank you for this great article, it helped me setup my first EB environment and configure Circle CI workflow.

Leave a Reply to Anonymous Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.