Fully Serverless DERN Stack TODO App Pt. 1 - (DynamoDB, Express, React, Node)
Tags: serverless, node, javascript, aws
Published: 2022-03-01 21:41:06

## Pt. 1 - Setting up our Backend API and deploying to AWS

**Update 3/2/2022** [Pt. 2](https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk) is now published.  

[Completed Pt.1 Github Repo](https://github.com/akatora28/serverless-dern-todo-pt1)

Sorry for doing a [boring TODO app](https://www.freecodecamp.org/news/every-time-you-build-a-to-do-list-app-a-puppy-dies-505b54637a5d/), I figured there were enough moving parts with this write-up between, Express, React, AWS, Serverless, etc that making a ***very*** simple application would be welcomed. I'm also assuming that for this tutorial you already have some basic experience with AWS, AWS CLI, Express.js & Node.js but I'll try to make everything as beginner friendly as I can.
___

The MERN stack (MongoDB, Express, React, Node.js) is one of the most popular stacks among Node.js developers. However, this stack has a major achilles heel. 

It ***requires*** servers \*shudders*.  

Even if you do deploy your code to the cloud via a FaaS (Functions as a Service) platform, that pesky **M** in the **M**ERN stack, aka MongoDB needs a to be backed by a server. Either self-hosted, ie. via an EC2 instance running on AWS, or via a managed service, like [MongoDB Atlas](https://www.mongodb.com/atlas/database) (which, also runs their instances on AWS EC2 but it has a very nice interface.)  

What if, we could build a ***truly*** serverless Express.js API, with a React SPA Frontend?

Well, now we can.  

AWS offers [DynamoDB](https://aws.amazon.com/dynamodb/), a managed NoSQL database that can offer blazing-fast single-digit millisecond performance.  

Additionally, the node.js library [Dynamoose](https://dynamoosejs.com/getting_started/Introduction) is a modeling tool for DynamoDB that is very similar to the highly popular [Mongoose](https://mongoosejs.com/) for MongoDB. Developers already familiar with the MERN stack should feel right at home using Dynamoose with minimal modifications.

Plus, with a little deployment magic help from [Claudia.js](https://claudiajs.com/), we have a very easy way to build and deploy serverless Express.js apps.

Finally, we'll build out a React SPA frontend, and deploy that on AWS Cloudfront so that we're getting the benefits of having our static code and assets delivered via a global CDN.  

___
**Side Note:** I'm really playing up the "negatives" of servers & databases for dramatic effect. Servers actually aren't that big and scary. In the real-world, the backend needs of every application will obvioiusly vary greatly. Serverless is a great tool to have in the toolbelt, but I don't believe it should be the end-all be-all for every situation.
___

## Getting Started
Let's start by setting up our project directory. I'm going to start by making my project directory called `dern-todo`, then inside that directory I'm also going to create a directory called `backend`.  
```
mkdir dern-todo && cd dern-todo
mkdir backend && cd backend
```

We're going to keep all of our Express.js / Claudia.js code inside the `/backend` directory, and when we eventually create a React frontend SPA, it will live-in, unsurpisingly, a directory called `frontend`.

Make sure you're in the `backend` directory, then initialize our backend application with NPM init.
```bash
npm init
```

I'm going to use all the NPM defaults except for 2 things. 1.) I'm changing the package name to `dern-backend` instead of just `backend`, which is pulled in from the directory name.

2.) I'm going to change "entry point: (index.js)" to app.js, which is what we'll use for our Claudia.js setup

```bash
❯ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (backend) dern-backend
version: (1.0.0) 
description: 
entry point: (index.js) app.js
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/[path]/dern-todo/backend/package.json:

{
  "name": "dern-backend",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 
```

From our `/backend` directory, let's go ahead and install `express`. We'll also install `nodemon` and save it as a dev-depency to automatically restart our server on code changes.

```bash
npm install express
npm install --save-dev nodemon
```

Next, house-keeping item, I like to put all code assets into a `/src` directory to help keep things organized.

Then, after we create that directory, we'll also create our app.js file, PLUS an app.local.js which we'll use to run our app locally while testing.

```bash
mkdir src && cd src
touch app.js
touch app.local.js
```

Now we'll setup a _very_ simple express to get everything setup for further development.  

Thanks to [attacomsian](https://attacomsian.com/blog/express-js-aws-lambda-claudia-serverless-app) for a great Claudia.js setup which I'm basing the Claudia.js portion of this write-up on.

***backend/src/app.js***
```js
const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello world!'))

module.exports = app;
```

Then, our app.local.js file

***backend/src/app.local.js***
```js
const app = require('./app')
const port = process.env.PORT || 3000

app.listen(port, () => 
  console.log(`App is listening on port ${port}.`)
)
```

Finally, edit `backend/package.json` to add the following script:

```json
{
  "name": "dern-backend",
  ...
  "scripts": {
    "dev": "nodemon src/app.local.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ...
}
```

We can confirm that our express app works by running the following command:

```bash
npm run dev
```

You should see the following output:
```bash
❯ npm run dev

> dern-backend@1.0.0 dev
> nodemon src/app.local.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/app.local.js`
App is listening on port 3000.
```

With that up and running, let's get the Claudia.js stuff configured so we can deploy our app to AWS. First, you can check if you already have Claudia installed on your system by running:  
```bash
claudia --version
```

If you see a version number returned, ie `5.14.0`, you're all set. If not, you can install Claudia.js globally with the following command:

```bash
npm install -g claudia
```
Note that we're using the `-g` flag with NPM to install the claudia package globally.

After that completes, you can confirm the installation was successful by running the above `claudia --version` command.

With Claudia, successfully installed, we're ready to use it to generate AWS Lambda wrapper. Run the following command from the `/backend` directory:

```bash
claudia generate-serverless-express-proxy --express-module src/app
```

You should see the following output in the terminal:

```bash
❯ claudia generate-serverless-express-proxy --express-module src/app
npm install aws-serverless-express -S

added 3 packages, and audited 171 packages in 2s

18 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
{
  "lambda-handler": "lambda.handler"
}
```

Note that in our backend directory, a new file `lambda.js` has been created. This file has configuration values for claudia.  

With that in-place, we're almost ready to do an initial deploy to AWS. We'll just need to make sure we've [configured the AWS CLI & credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). 

Sure, at the moment our express "app" is just a simple "Hello, World!", but let's make sure we're deploying early & often so we can work out any bugs / differences between local and AWS.

Run the following command:
```bash
claudia create --handler lambda.handler --deploy-proxy-api --region us-east-1
```

This will take a little bit of time to run, as claudia is doing some important stuff for us automatically, but you should see status updates in your terminal. Once it completes, you should see a json output with some information about our claudia app.

```json
saving configuration
{
  "lambda": {
    "role": "dern-backend-executor",
    "name": "dern-backend",
    "region": "us-east-1"
  },
  "api": {
    "id": "[api-id]",
    "url": "https://[api-id].execute-api.us-east-1.amazonaws.com/latest"
  }
}
```

If you're unfamiliar with AWS services such as Lambda & API Gateway, I'll briefly explain. Lambda is AWS's "Functions As A Service" platform, it allows to upload code (in our case node.js code) and run it on-demand, as opposed to needed to deploy, provision and manage node.js servers.  

There's a variety of [ways you can invoke your Lambda function](https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html) once it's uploaded onto AWS, but the method we're going to be using (through Claudia.js that is), is via an API Gateway.  

[API Gateway](https://aws.amazon.com/api-gateway/) is a service that allows to deploy API's on AWS. One of the ways API Gateway works is by allowing you to specify various endpoints, and invoke specific Lambda functions when a request is made to that endpoint. 

Although manually defining endpoints in AWS can be a useful method to build and deploy micro-services, Cluadia.js allows us to deploy our express app as a single Lambda function, and uses a [proxy resource with a greedy path variable](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html) to pass the endpoints to our express app. 

Below is what you'll see in the AWS Console for API Gateway after Claudia finishes deploying.

![API Gateway view of deployed Claudia.js App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6gw9jca8qtcd7dnnnnz.jpg)

I won't go into too much detail here about the various settings and configurations of API Gateway, but the laymans version of how to interpret the image above is that API Gateway will pass any HTTP requests, ie. `POST /api/auth/login {"user":"username":"pass":"password"}` (that's just psuedo-code), to our Lambda function, which is an Express.js app, and the Express.js App Lambda function will handle the request the same way it would if the app were running on a server.  

If that sounds complicated, don't worry, we'll run through a quick example to see how everything is working.  

Throughout the rest of this write-up / series, I'm going to be using [Postman](https://www.postman.com/) to test out our api until we build out a frontend. I'm going to keep all related requests in a Postman collection named "Serverless DERN TODO". Going into too much detail on Postman is going to be outside the scope of this tutorial, but I'll try to explain what I'm doing each step of the way in case this is your first time using the tool.

If you'll recall back to our `app.js` file from earlier, you'll remember that we setup a single `GET` endpoint at our API root. Let's use Postman to make a `GET` request there and confirm everything is working.

The URL that we will make the request to is the url from the Claudia json output earlier:
```json
{
  "lambda": {
    ...
  },
  "api": {
    "id": "[api-id]",
    "url": "https://[api-id].execute-api.us-east-1.amazonaws.com/latest" <- This thing
  }
}
```

If you need to find that information again, you can either go into the AWS API Gateway console, click "Stages", then "latest". The URL is the "Invoke URL". 

![Finding API ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/so7ubbnhz4y17js68r8s.jpg)

Or, you'll notice that after we ran the `claudia create ...` command earlier, a new claudia.json file was created which stores our api-id & the region we deployed our api to, in this case us-east-1. You can take those two values and put them into the following URL pattern

```bash
https://[api-id].execute-api.[aws-region].amazonaws.com/latest
```

___
**Note:** The `/latest` path at the end of our Invoke URL is the "stage" from API Gateway. You can configure multiple stages (ie dev, v1, etc) but the default stage Claudia creates for us is "latest". Express will start routing after the `/latest` stage. For example, if we made a `/login` endpoint, the final URL would look like `https://[api-id].execute-api.[aws-region].amazonaws.com/latest/login`
___

Here's our Postman `GET` request to the API root. We get back, `Hello world!`

![Postman Hello World](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gimtl0au0c7ghrkkh9k.jpg)

Don't forget, we also setup our `app.local.js` file so that we can develop and test on our local machine. Run the `npm dev` command to startup our express app.

```bash
npm run dev

> dern-backend@1.0.0 dev
> nodemon src/app.local.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/app.local.js`
App is listening on port 3000.
```

I'm also going to change our Base URL to a Postman variable. Highlight, the entire url in our request, click the "Set as variable" popup that appears, then select, "Set as a new variable". I'm naming my variable `BASE_URL` and setting the scope to the collection. Finally, click the orange "Set variable" button to save.  

![Promote URL to variable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0nflgqlq9cg3unzwfzl9.jpg)

![Naming our variable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7y7d652mjfgh8htp10nj.jpg)

If all went correctly, you should see the url in the `GET` request changed to `{{BASE_URL}}`.

![It worked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dynvtrycxxhi7ab9mbfb.jpg)

Now that we've promoted our API Gateway URL to a variable, it's time to immediately change its value to point to our localhost server.

Access the variables by clicking on the name of the collection in the left-hand sidebar (mine is named Serverless DERN TODO). Then click the "variables" tab, you should see `BASE_URL` the variable we just created. It has two fields, "INITIAL VALUE" & "CURRENT VALUE". Change the URL inside "CURRENT VALUE" to "http://localhost:3000".

**IMPORTANT!** Don't forget to save **BOTH** the collection and the `GET` request to ensure that Postman is using the updated value for the variable. The orange circles on the request and collection tabs will let you know if you have unsaved changes.

![Updating Postman variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hvwknzs2vdae144nl3ys.jpg)

You should be able to send the `GET` request again, and see the same `Hello world!` response. Right now, we don't have any logging in our app, so you won't see anything in the terminal running our local version of the app. The only difference you might notice is a significantly lower ms response time vs. the AWS API Gateway request, since our localhost version doesn't have very far to go.  

With all that setup, we're in a good place to stop for Part 1. We've accomplished a lot so far, we have an Express.js app setup and ready to easily deploy to AWS via Claudia.js. We also have a local dev version of our Express app ready for further development and testing.  

Up next is [Pt. 2 of the series](https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk) where we'll start foucsing on building out the features of our application like building some data models with Dynamoose.