Serverless Shopping Cart with OpenJS Architect
Published: 2021-04-05 15:34:10

In this post, we'll be looking at creating a model of a shopping cart. We will be using [OpenJS Architect](https://arc.codes) for the infrastructure as code and deployment framework. We will be deploying to AWS cloud through [Begin](https://begin.com). No other frameworks will be needed as we'll be developing this app with just HTML forms, GET, and POST requests. The cart will even work with javascript disabled! 

# Create a new Architect project 

We can create a new Architect project from the command line, the only prerequisite is having Node.js installed. 

```
npm init @architect ./shopping
cd shopping
npm i @architect/functions @begin/data
```

We're adding two dependencies that we will need to make things easier, @architect/functions and @begin/data. These two libraries will make things easier as we work with AWS Lambda functions and DynamoDB. 

# Modify the `app.arc` file 

The `app.arc` file is our infrastructure as code file. It tells Architect what resources to deploy and gives structure to the underlying source code. Open up the `app.arc` file in the root of your project and modify it with the following: 

```
@app
shopping

@http
get /
post /cart
post /cart/delete

@tables
data
  scopeID *String
  dataID **String
  ttl TTL
```

After you modify the `app.arc` file, run `arc init` from the command line to scaffold out the functions that we will need. 

There are three routes and a declaration of a data table. Each route will have a corresponding Lambda handler that is a discrete function. We'll be using @begin/data as a DynamoDB client to interact with the database. 

# Modify the `get-index` route 

The first route we'll look at is the `get-index` route, which is called at the apex of the application. 

```javascript
let arc = require('@architect/functions')
let data = require('@begin/data')

async function http (req) {

  let result = await data.get({
    table: 'shopping-cart'
  })

  return {
    statusCode: 200,
    headers: {
      'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0',
      'content-type': 'text/html; charset=utf8'
    },
    body: `
    <h1> Praise Cage </h1>
    <p> Welcome to the Store </p>
    <ul>
    <li>Item 1</li>
    <form action='cart' method='POST'>
    <input type="number" name="quantity"/>
    <input type="hidden" name="productId" value='001'>
    <button>Add to Cart</button>
    </form>
    <form action='cart/delete' method='POST'>
    <input type="hidden" name="productId" value='001'>
    <button>Remove from Cart</button>
    </form>
    <li>Item 2</li>
    <form action='cart' method='POST'>
    <input type="number" name="quantity"/>
    <input type="hidden" name="productId" value='002'>
    <button>Add to Cart</button>
    </form>
    <form action='cart/delete' method='POST'>
    <input type="hidden" name="productId" value='002'>
    <button>Remove from Cart</button>
    </form>
    <li>Item 3</li>
    <form action='cart' method='POST'>
    <input type="number" name="quantity"/>
    <input type="hidden" name="productId" value='003'>
    <button>Add to Cart</button>
    </form>
    <form action='cart/delete' method='POST'>
    <input type="hidden" name="productId" value='003'>
    <button>Remove from Cart</button>
    </form>
    </ul>
    <p>This is your order</p>
    <ul>${result.map(item => `<li>${JSON.stringify(item.cartItem)}</li>`).join('')}</ul>
    `
  }
}

exports.handler = arc.http.async(http)
```
In this function, we are returning a string of HTML that will be rendered by the browser. Congratulations, you now have done server-side rendering with a serverless function! 

# Adding a database layer

You should notice that we are using @begin/data in the `get-index` route to grab all the cart data before the page loads and we will render that at the bottom of the page. In a future tutorial, we will look at styling this page, but for now, we will just return JSON of the objects in the database.

# Modify the `post-cart` route 

The `post-cart` route is where we will update our cart with the different items that are available. 

```javascript
let arc = require('@architect/functions')
let data = require('@begin/data')

async function http (req) {

  await data.set({
    table: "shopping-cart",
    key: req.body.productId,
    cartItem: {
      quantity: req.body.quantity,
      prodId: req.body.productId
    }
  })

  return {
    statusCode: 302,
    location: '/'
  }
}

exports.handler = arc.http.async(http)
```

# Modify the `post-cart-delete` route

The `post-cart-delete` handler is responsible for deleting the item from the cart. Again it uses the @begin/data DynamoDB client. For more information about @begin/data, [check out the docs](https://docs.begin.com/en/data/getting-started). 

```javascript
let arc = require('@architect/functions')
let data = require('@begin/data')

async function http(req) {

  await data.destroy({
    table: "shopping-cart",
    key: req.body.productId
  })

  return {
    statusCode: 302,
    location: '/'
  }
}

exports.handler = arc.http.async(http)
```
With this last handler, we can now run `npm start` and see the results of our app thus far. 

You should be seeing an unstyled page. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gi2smsgmdtn897pq6pp.png)

# Next steps
In a follow-up tutorial, we will take a look at styling the cart so it will look a bit nicer.

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

Full code repo can be found at [https://github.com/pchinjr/shopping-cart](https://github.com/pchinjr/shopping-cart)