Express style API with OpenJS Architect
Tags: serverless, cloud, architect, javascript
Published: 2020-10-23 12:37:04

## Foundations
We'll cover everything you need to build and deploy a RESTfull serverless API with AWS HTTP APIs, Lambda Functions, and CI/CD on Begin. 

The following examples are entirely based on [Free Code Camp's APIs and Micro-services Certification: Basic Node and Express](https://www.freecodecamp.org/learn/apis-and-microservices/basic-node-and-express/) but with [serverless architecture](https://arc.codes). Including single-responsibility functions, shared middleware, and static hosting with automated deployments through [Begin](https://begin.com).

We will be building with [OpenJS Architect](https://arc.codes), a serverless deployment framework that focuses on AWS services to build web apps.

## Why have I done this?
Serverless is another tool for web developers to develop applications and deploy them to scalable infrastructure. FreeCodeCamp enabled me to take on a whole new career and taught me life fulfilling skills. I wanted to give back and encourage new and old developers to look at cloud functions instead of stateful server processes. In this project, the FCC API is replicated using serverless technologies and deployed with Begin CI/CD. Requirements include Node.js and a Github account. No AWS Account is needed because we will deploy with Begin CI/CD.

## Clone repo and local development

The first step is to click the button to deploy this app to live infrastructure with Begin.

[![Deploy to Begin](https://static.begin.com/deploy-to-begin.svg)](https://begin.com/apps/create?template=https://github.com/pchinjr/fcc-serverless-api)

Underneath, Begin will create a new GitHub repo to your account that you can clone to work on locally. Each push to your default branch will trigger a new build and deploy to the `staging` environment. Your CI/CD is already complete!!

When your app deploys, clone the repo, and install the dependencies.
```bash
git clone https://github.com/username/begin-app-project-name.git
cd begin-app-project-name
npm install
```

## Project structure
Your source code is primarily in `/src`. Each `HTTP` function represents a discrete endpoint with self-contained logic. For example, `get-index` contains the response code of a GET request to the root of your application. Static assets and items that would usually be behind a CDN are in `/public`. The `app.arc` file is a manifest that describes your source code and the resulting AWS infrastructure. `/src` and `/public` are the only folders that get deployed.
```bash
fcc-serverless-api
├── public
│   └── default.css
│   └── me_begin.jpg
└── src
   └── HTTP
      └── get-index
         └── index.js
         └── package.json
```


## Function logs and the Node console
`console.log('got here')` is probably my most used debugging tool. It's a simple way to walk through your code execution. To view logs in Begin, go to your Begin console and inspect the route you want.  When your function is invoked, it behaves as if it is being run for the first time. This is different from a regular Express server that is assumed to be long-living and can retain data between route invocations. 

If you are eager to skip ahead to sessions and data persistence, check out [https://learn.begin.com](https://learn.begin.com)

Let's look at a `console.log()` statement to the `get-index` function.

```js
// src/http/get-index/index.js

let body = `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Hello HTML</title>
    <link rel="stylesheet" href="_static/default.css">
  </head>
  <body>
    <h1>Hello Beginners!</h1>
    <img src="_static/me_begin.jpg">
    <p>Oh yea, wait a minute</p>
     <form action="/name" method="post">
      <label>First Name :</label>
      <input type="text" name="first" value="Mr."><br>
      <label>Last Name :</label>
      <input type="text" name="last" value="POSTman"><br><br>
      <input type="submit" value="Submit">
    </form>
  </body>
</html>
`
// main Lambda function handler, returns an HTTP response with an HTML string in the body.
exports.handler = async function http(req) {
  console.log('Praise Cage')
  return {
    statusCode: 200,
    headers: {
      'content-type': 'text/html; charset=utf8',
      'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
    },
    body
  }
}
```
Now when you visit your index route from your staging link, you should see the console output:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p9q48kaemnjq2rfxf0qd.png)

## Serverless HTML and static assets

In the FCC Express challenge, they show you how to create a web server by opening a port to listen on. With serverless functions, you don't need to create that layer of code. HTTP requests are handled by AWS API Gateway, a service that will act as part of your web server. When users make a request, each route is handled by a Lambda Function. This gives us the ability to only write logic that pertains to the request and response needed by a single route. It also has added security because the control of that function is only allowed by your app on your Begin domain. Architect takes care of IAM roles and service permissions when your code is deployed.

The combination of code and the underlying infrastructure is called "Infrastructure as Code". We achieve this by writing a manifest called `app.arc` in the root of the project. Architect captures cloud resources and associated function code in a single file.

So let's take a look at it now.

```md
# app.arc
@app
fcc-apis   # app namespace - this helps organize the backend resources

@static    # declaration of static assets, defaults to the /public folder

@http      # declaration of HTTP routes, each route has it's own function handler organized by folder
get /      # the function handler is found in /src/http/get-index/index.js
```

Each function is self contained in it's own function folder according to route and HTTP method. One failing function won't take down the entire app, just the code behind that route.

To start serving HTML and static assets, we can put them into the `/public` folder. Notice that the image served from `/public` is referenced with `_static`. Take a look at line 13, `<img src="_static/me_begin.jpg">`.

## Serve JSON on a specific route

The heart of a REST API is specifying some action with a URL path, and an HTTP method. The method is defined by `app.arc`, which tells API Gateway how to interpret the HTTP request on a route. That path could return JSON data, an HTML string, or any other kind of text. In this section, we want to return JSON at the route `/json`. Setting it up means adding this route to `app.arc` and writing a `get-json` handler function.

```md
# app.arc
@http
get /json
```
```js
// src/http/get-json/index.js
exports.handler = async function http (req) {
  let message = "Praise Cage!"
  return {
    statusCode: 200,
    headers: {
      "content-type": "application/json; charset=utf-8"
    },
    body: JSON.stringify({"message": message})
  }
}
```
## Environment Variables
Environment variables are values that can be used during runtime. We typically hold sensitive information like API keys and configuration secrets that should not be stored in `.git`. In order to use environment variables with Sandbox, our development server, we need to create a `.arc-env` file. Then we can add `staging` and `production` environment variables in the Begin Console.

```
# .arc-env
@testing
MESSAGE_STYLE uppercase
```

Refactor `get-json` to check for the environment variable `MESSAGE_STATUS`

```js
// src/http/get-json/index.js
exports.handler = async function http (req) {
  let message = "Hello json"

  // new code to check for environment variable
  if (process.env.MESSAGE_STYLE==="uppercase") {
    message = message.toUpperCase()
  }

  return {
    statusCode: 200
    headers: {
      "content-type": "application/json; charset=utf-8"
    },
    body: JSON.stringify({"message": message})
  }
}
```
Add the environment variable in the Begin Console by navigating to "Environments", typing in your key and value, and clicking `add`. Note that there are different areas for `staging` and `production`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/if3gc8agrdao9b6pxm5b.png)

## Root-level request logger and middleware

In order to create a logger on every request, we can use a special folder called `src/shared` to create utilities that multiple functions can access. Since each function is isolated, Architect will copy everything in `src/shared` into the `node_modules` folder of every function. We will start with declaring a new route, writing a handler function, and writing a logger utility function.

```md
# app.arc
@http
get /now
```
```js
// src/shared/utils.js
function logger(req){
  // takes a request and logs the HTTP method, path, and originating public IP address.
  console.log(`${req.httpMethod} ${req.path} - ${req.headers['X-Forwarded-For']}`)
  return
}

module.exports = logger
```

Now you can add `logger()` to any function you want by requiring it at the top. We can combine the shared code with an Express style middleware in `@architect/functions` to complete the next challenge.

```bash
cd src/http/get-now/
npm init -y
npm install @architect/functions
```
```js
// src/http/get-now/index.js

// require logger and @architect/functions
let logger = require('@architect/shared/utils')
let arc = require('@architect/functions')

// first function call to modify the req object
function time(req, res, next) {
  req.time = new Date().toString()
  next()
}

// response function with response object
function http(req, res) {
  let time = `Praise Cage! The time is: ${req.time}`
  res({
    "json": {time: time}
  })
}

// arc.http registers multiple functions and executes them in order
exports.handler = arc.http(time, http)
```
`arc.http` registers multiple functions. Each function will get executed to modify the `req` object. If a function does not end the request/response cycle, it must call `next()` and the final function must call `res()`

To learn more about the `arc.http` request and response methods, check out https://arc.codes/reference/functions/http/node/classic.

## Get route(path) parameter input from the client

In this function, we will build an echo endpoint to respond with a JSON object of the word that is passed in as a request parameter. Add a new endpoint to `app.arc` and write a corresponding handler function.

```md
# app.arc
@http
get /echo/:word
```
```js
// src/http/get-echo-000word/index.js
exports.handler = async function http(req){
  let { word } = req.pathParameters
  return {
    statusCode: 200,
    headers: {
      'content-type':'application/json; charset=utf-8'
    },
    body: JSON.stringify({ echo: word})
  }
}
```
A GET request to `/echo/freecodecamp`, will result in a request object that has a property `pathParameters` with the object `{ word: 'freecodecamp'}` as a value. This is useful for dynamic routes like `users` or `postId` where the route can be appended with any string that you can catch and reference.

## Get query parameter input from the client

Another way to pass data to your API endpoint uses query parameters. We're going to add a `get-name` HTTP route with a corresponding handler.

```md
# app.arc
@http
get /name
```
```js
// src/http/get-name/index.js
exports.handler = async function http(req, res) {
  let { first, last } = req.queryStringParameters
  return {
    statusCode: 200,
    headers: {
      'content-type':'application/json; charset=utf-8'
    },
    body: JSON.stringify({
      "name": `${first} ${last}`
    })
  }
}
```
A GET request to `/name?first=nic&last=cage`, will result in a request object that has a property `queryStringParameters` with the object `{ first: 'nic', last: 'cage' }` as a value. We can treat this similarly to route parameters. A query parameter can give the backend certain keys to filter or sort items.

## Parse request bodies and data from POST requests
Another way to receive data is from a POST request as an HTML form. HTML forms allow the browser to submit data to the server-side without using JavaScript. The data is part of the HTTP payload in the request body. In this example, we are using `urlencoded` body. Architect uses Base64 encoded strings for all request bodies, and we have a helper method in `@architect/functions` to help parse request bodies. Since each function is isolated, we will have to install and manage dependencies per function folder.

But first, let's set up a `post-name` function and route.

```md
# app.arc
@http
post /name
```
Then we can install `@architect/functions` for the body parser.
```bash
cd src/http/post-name
npm init -y
npm install @architect/functions
```
Now let's write the function handler

```js
// src/http/post-name
let arc = require('@architect/functions')

exports.handler = async function http(req) {
  let {first, last} = arc.http.helpers.bodyParser(req)
  return {
    statusCode: 200,
    headers: {"Content-type": "application/json; charset=UTF-8"},
    body: JSON.stringify({
      name: `${first} ${last}`
    })
  }
}
```
Now you can use `index.html` to submit a form with any name you would like, i.e. Nic Cage, and the `post-name` handler with reply with `{ "name": "Nic Cage"}`.

## Infrastructure as Code
This is a serverless approach to building a REST API and serving static assets. Take a look at your final `app.arc` file, and you will see an entire rundown of your entire app.

```md
# app.arc
@app
fcc-apis

@static

@http
get /             # root proxy to static assets
get /json         # deliver JSON data
get /now          # middleware example
get /echo/:word   # get path parameters
get /name         # get query string parameters
post /name        # process HTML Form data
```

Each commit to your default `.git` branch triggers a deploy to `staging` on Begin. When you are ready for production, click `Deploy to Production` in your Begin Console and say "Hello" to Ship-it Squirrel.

>> For extra funsies, you can see the original FCC Express app with all the same capabilities, running in a single giant Lambda function. You can see that code here: [https://github.com/pchinjr/boilerplate-express](https://github.com/pchinjr/boilerplate-express)