Set up a CD workflow for NodeJS to AWS Lambda using GitHub Actions
Tags: node, serverless, github, aws
Published: 2020-11-29 18:48:08

##What We'll Be Doing
In this article we'll be automating the deployment process of your code with AWS Lambda using NodeJS using CD with GitHub Actions. 

You can scroll down all the way to the bottom to see the final code if you want

##What is Continuous Deployment(CD)? 
[This Article](https://www.atlassian.com/continuous-delivery/continuous-deployment) does a great job of explaining it. 
>Continuous Deployment (CD) is a software release process that uses automated testing to validate if changes to a codebase are correct and stable for immediate autonomous deployment to a production environment.

There are many softwares you can use to set up Continuous Deployment such as [Jenkins](https://www.jenkins.io/), [Travis CI](https://docs.travis-ci.com/user/deployment), and [CircleCI](https://circleci.com/docs/2.0/deployment/). But the one we're using is GitHub Actions

##What is GitHub Actions?
For more information about GitHub Actions checkout [this article](https://docs.github.com/en/free-pro-team@latest/actions)
>Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you'd like, including CI/CD, and combine actions in a completely customized workflow.

##Getting Started
To deploy code you would have to use the [Serverless CLI](https://www.serverless.com/framework/docs/getting-started/) 
We'll be automating this process using GitHub Actions. 

We'll be creating an `app.js` file, you can change it to however you like. 

First we'll be using [Express](https://www.npmjs.com/package/express) for our web framework. This is optional but we'll also be using [dotenv](https://www.npmjs.com/package/dotenv) and setting up environment variables. You will need it if you want to use confidential data such as API Keys, DB credentials, etc. 

This is an example of what I did in `app.js`:
```javascript
const express = require('express');
const app = express();
require('dotenv').config();

app.get('/', (req, res) => {
  res.send('hello world');
})

app.get('/secret', (req, res) => {
  res.send(process.env.API_KEY)
})

app.listen(process.env.PORT || 3000);
```

###Setting up GH Actions
Setting up GitHub Actions is quite simple first a folder named `.github`, inside that create a `workflows` folder and inside that folder create a yaml file with any name of your choice, we're doing `deploy.yaml`. It should look something like this:

`.github/workflows/deploy.yaml`

Good job! You've set up a GitHub Actions workflow! You can view it under the Actions tab for your Github repository.

###Setting up Serverless
First install the [Serverless CLI:](https://www.npmjs.com/package/serverless)
> npm install -g serverless

Then run:

> serverless create -t aws-nodejs

This will create 2 files, `handler.js`, and `serverless.yaml`. You can delete the handler.js file as its just a demo and remove just about everything in the serverless.yaml. But leave these code:
```yaml
service: Article_Test
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x

functions:
  hello:
    handler: handler.hello
```
Right now it won't work with express, to solve this we're going to be using [serverless-http](https://www.npmjs.com/package/serverless-http).
Make these changes in your `app.js` file:
```javascript
const express = require('express');
const app = express();
const serverless = require('serverless-http');
require('dotenv').config();

app.get('/', (req, res) => {
  res.send('hello world');
})

app.get('/secret', (req, res) => {
  res.send(process.env.API_KEY)
})

module.exports.handler = serverless(app)

app.listen(process.env.PORT || 3000);
```
Notice we made the `module.exports.handler = serverless(app)`
That part is important because in the `serverless.yaml` we need to make these changes:
```yaml
service: Article-Test
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x

functions:
  app:
    handler: app.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
```
Notice the: `handler: app.handler`. We exported that in the `app.js` and it is looking for any exported function named `handler` in the `app.js` file. Which we did here: `module.exports.handler = serverless(app)`. If you don't get the name of the file or function exported right it will cause an error.
> **Reminder:** API Gateway won't know any of express' routes defined

This is the final part before getting serverless do deploy and that's setting up your AWS credentials. Get your AWS key and secret by going into the AWS Console. Under `Profile > My Security Credentials`. You can create a new key. Set the credentials with the serverless CLI using this command:
>serverless config credentials --provider aws --key AKIAIOSFODNN7EXAMPLE --secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

>**Reminder:** If you are using AWS educate find your credentials on [Vocareum](https://labs.vocareum.com/main) and click on 'Account Details'. Then copy and paste your credentials into the `~/.aws/credentials` file.

Obviously put your credentials. You can view it under the file `~/.aws/credentials`
It should look something like this:
>aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKY

Great! We're almost done setting up serverless! Now run the command:
`serverless deploy`

Your output should look something like this:
```
Serverless: Stack update finished...
Service Information
service: Article-Test
stage: dev
region: us-east-1
stack: Article-Test-dev
resources: 12
api keys:
  None
endpoints:
  ANY - https://ob74bjk993.execute-api.us-east-1.amazonaws.com/dev
  ANY - https://ob74bjk993.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
  app: Article-Test-dev-app
layers:
  None

***********************************************************************************************************************
Serverless: Announcing an enhanced experience for running Express.js apps: https://github.com/serverless-components/express.
```
This is your where you can find your API endpoint: 
```
endpoints:
  ANY - https://ob74bjk993.execute-api.us-east-1.amazonaws.com/dev
```
###Automate Using GitHub Actions
Finally, let's get back to the `deploy.yaml` file.
First up let's set up the trigger on what event should this workflow be running on. 
```yaml
on:
  push:
    tags:
    - 'v*.*.*'
```
This will run on any pushes with a tag that begins with a `v` for example `v1.0.0`.

The next step is what we're doing on this event.
First off add this:
```yaml
jobs:
  serverless-deploy-production:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with: 
          node-version: 12
```
This will run any commands we did on an Ubuntu VM. This part:
```yaml
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with: 
          node-version: 12
```
This will get your source code into the machine and will set up NodeJS. 

The next part is this:
```yaml
      - name: Install serverless CLI
        run: npm install -g serverless
      - name: Install npm dependencies
        run: npm install 
```
This will install the serverless cli on the VM and install npm dependencies

The next part is optional, if you don't need environment variables then you can skip this.
```yaml
 - name: set environment variables
        run: |
          touch .env
          echo "API_KEY=${{secrets.API_KEY}}" >> .env
```
GitHub Actions will create a `.env` file and will redirect the output to the `.env` file

This will get your secrets from your GitHub repo. To set GitHub secrets:
![Setting up secrets with GitHub](https://dev-to-uploads.s3.amazonaws.com/i/xibm9z75jyow7tszmxty.png)
That is where the values of your environment variables are going to come from.

This is the final parts:
```yaml
      - name: deploy
        run: |
          serverless config credentials --provider aws --key ${{secrets.AWS_ACCESS_KEY}} --secret ${{secrets.AWS_SECRET_ACCESS_KEY}}
          serverless deploy --stage production
```
> **Note**: If you're an IAM user and you have a session token setting up the credentials using `serverless config` won't allow you to configure a session storage. To solve this use the following code I provided at the end

This will set up the credentials for us so we don't need to install the serverless CLI and set credentials ever again. 

I also added `--stage production` for production flag but you can remove that if you want.

Your `app.js` should look something like this:
```javascript
const express = require('express');
const app = express();
const serverless = require('serverless-http');
require('dotenv').config();

app.get('/', (req, res) => {
  res.send('hello world');
})

app.get('/secret', (req, res) => {
  res.send(process.env.API_KEY)
})

module.exports.handler = serverless(app)

app.listen(process.env.PORT || 3000);
```
Your `serverless.yaml` file should look like this:
```yaml
service: Article-Test
frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs12.x

functions:
  app:
    handler: app.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
```
Your `deploy.yaml` should look something like this:
```yaml
on:
  push:
    tags:
    - 'v*.*.*'
jobs:
  serverless-deploy-production:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with: 
          node-version: 12
      - name: Install serverless CLI
        run: npm install -g serverless
      - name: Install npm dependencies
        run: npm install 
      - name: set environment variables
        run: |
          touch .env
          echo "API_KEY=${{secrets.API_KEY}}" >> .env
      - name: deploy
        run: |
          serverless config credentials --provider aws --key ${{secrets.AWS_ACCESS_KEY_ID}} --secret ${{secrets.AWS_SECRET_ACCESS_KEY}}
          serverless deploy --stage production
```
If you are a user with session token then your file should look something like this:
```yaml
on:
  push:
    tags:
    - 'v*.*.*'
jobs:
  serverless-deploy-production:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with: 
          node-version: 12
      - name: Install serverless CLI
        run: npm install -g serverless
      - name: Install npm dependencies
        run: npm install 
      - name: set environment variables
        run: |
          touch .env
          echo "API_KEY=${{secrets.API_KEY}}" >> .env
      - name: Configure AWS credentials for account with session token
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID}}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-session-token: ${{secrets.AWS_SESSION_TOKEN}}
          aws-region: us-east-1
      - name: deploy
        run: serverless deploy --stage production
```
Of course please configure your AWS credentials.

To see it in action use the following commands:
>git tag v1.0.0
git push origin v1.0.0

Once successful it should look something like this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p3mhh96tbjgbuyz7p3do.png)

You can check out my repository here:
https://github.com/destroyer22719/GH-Actions-NodeJS-Article-Repo

>**Note:** If you try to connect to the URL and you see `{
  "message": "Missing Authentication Token"
}`, don't worry, check out [this article](http://www.awslessons.com/2017/aws-api-gateway-missing-authentication-token/) to solve this issue. 

##Conclusion
Congratulations! You've (hopefully) successfully deployed your code to the cloud with Continuous Deployment!

I was the one who deployed the code to the cloud and set up the CD workflow in my work and it was a really amazing (and frustrating) learning experience for me. I made this article to guide you to CD with AWS Lambda. 

Thanks for reading my first post and happy coding!