Typescript and the Beanstalk
Tags: typescript, aws, beanstalk, circleci
Published: 2018-06-16 15:10:38

---
title: Typescript and the Beanstalk
published: true
description: Deploying typescript apps to Beanstalk with CircleCI
tags: typescript, aws, beanstalk, 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](https://medium.com/engineering-on-the-incline/continuous-integration-with-circleci-2-0-github-and-elastic-beanstalk-cf1a0b7f05c6) that explains how to set that up.

Also, a big special thanks to [Rokt33r](https://github.com/Rokt33r/aws-eb-typescript-app) 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 `dist`ribution `.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: Amend 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 amend 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 :)