Skip to main content

Deploy an API to AWS

In this tutorial, we will develop and deploy a simple Node.js REST Api with mongodb to AWS using Hereya.

In the following steps, you will learn how to:

  • Add mongodb database to your project with a single command.
  • Develop a simple Node.js REST Api.
  • Deploy your app to AWS with a single command.

Prerequisites

To follow this tutorial, you need to have the following:

  • An AWS account and AWS CLI installed on your machine and configured with your AWS account credentials. For this tutorial we recommend using a development AWS account and not the account you use for production. Make sure that you have admin access to the AWS account.
  • Node.js version 18.0 or above.
  • Docker: hereya will use docker to run a mongodb database locally.

What you will build

You will build a simple Node.js REST Api that connects to a mongodb database. The app is a simple movies REST Api with the following endpoints:

  • GET /movies: Get all movies.
  • POST /movies: Create a new movie.
  • GET /movies/:id: Get a movie by id.
  • PUT /movies/:id: Update a movie by id.
  • DELETE /movies/:id: Delete a movie by id.

Install Hereya

You can install Hereya by running the following command:

npm install -g hereya-cli 

Set up the Node.js project

Create a new directory for your project and navigate to it:

mkdir hereya-tutorial-aws # You can name it whatever you want
cd hereya-tutorial-aws

Initialize a new Node.js project:

npm init -y

Set the type of your project to module in the package.json file:

{
"type": "module"
}

Your package.json file should look like this:

{
"name": "hereya-tutorial-aws",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

Initialize Hereya in your project

When hereya adds packages to your app, it installs them in the context of a workspace. A workspace is like an execution environment for your app. You might have for example dev, staging, and production workspaces.

Create a new workspace named dev:

hereya workspace create dev

Initialize Hereya in your project:

hereya init hereya-tutorial-aws -w dev # Replace hereya-tutorial-aws with the name of your project

This command will create a hereya.yaml file in the root of your project with the following content:

name: hereya-tutorial-aws
workspace: dev

Write the Node.js REST Api

First, install the required packages:

npm install express body-parser

Create a new file named index.js in the root of your project and add the following code:

import express from 'express';
import bodyParser from 'body-parser';

const app = express();

app.use(bodyParser.json());

app.get('/', (req, res) => {
res.send(
`Welcome to Movies API. You can use this API to get and add movies at /movies.`
);
});

app.get('/movies', async (req, res) => {
res.json([]);
});

app.get('/movies/:id', async (req, res) => {
const id = req.params.id;
// todo: get movie by id
res.json({});
})

app.post('/movies', async (req, res) => {
const {name, year} = req.body;
// todo: create movie
res.json({})
});


app.put('/movies/:id', async (req, res) => {
const {name, year} = req.body;
const id = req.params.id;
// todo: update movie
res.end();
});

app.delete('/movies/:id', async (req, res) => {
const id = req.params.id;
// todo: delete movie
res.end();
});


const port = process.env.PORT || 8080;


app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

In this code snippet, we created a simple express server with /movies endpoints. You can run node index.js to start the server and test the endpoints.

We will implement the logic for storing and retrieving movies from a mongodb database in the next steps.

Add mongodb to your project

In this step, we will add a mongodb database to our project with a single command.

Without hereya, you would need to set up a mongodb database, configure the connection string, and provide it as an environment variable to your app.

With hereya, you can do all of these steps and more with a single command. hereya manage infrastructure through reusable infrastructure as code modules called packages.

Let's add the package hereya/mongo to our project. Make sure Docker is running on your machine.

hereya add hereya/mongo

This command deploys a mongodb database in a docker container and save the connection string to be used in your app.

if you open the file hereya.yaml, you will see that the hereya/mongo package has been added to your project:

name: hereya-tutorial-aws
workspace: dev
packages:
hereya/mongo:
version: ""
onDeploy:
pkg: hereya/aws-documentdb
version: ""

hereya.yaml should be checked into your version control system. It is similar to package.json in a Node.js project.

You can notice that the hereya/mongo package has an onDeploy section. This section specifies that when you deploy your project with the command hereya deploy, the package hereya/aws-documentdb will be deployed instead of hereya/mongo which can only be used in a local environment. We will see how to deploy the project to AWS later.

Connect to the mongodb database

hereya/mongo exports the following variables: mongoUrl, mongoDbname. hereya/aws-documentdb, which will be used in the AWS environment, exports additional variables: mongoUsername and mongoPassword.

You can print the environment variables exported by all installed packages by running:

hereya env -l

You don't need to manage these environment variables manually. Hereya will automatically set them in your app when you run it with the hereya run command.

In order to connect to mongodb, we need a mongodb client. You can use any client you want, but in this tutorial, we will use the mongodb npm package.

Install the mongodb package:

npm install mongodb

Now create a new file named db.js in the root of your project and add the following code:

import {MongoClient, ObjectId} from "mongodb";

const dbName = process.env.mongoDbname;
let mongoUrl = process.env.mongoUrl;
const username = process.env.mongoUsername;
const password = process.env.mongoPassword;


export const mongoClient = new MongoClient(mongoUrl, password ? {
auth: {username, password},
authMechanism: "SCRAM-SHA-1",
} : {})


export async function createMovie({name, year}) {
await mongoClient.connect();
const result = await mongoClient.db(dbName).collection('movies').insertOne({name, year});
return result.insertedId;
}

export async function getMovies() {
return mongoClient.db(dbName).collection('movies').find({}).toArray();
}

export async function getMovie(id) {
return mongoClient.db(dbName).collection('movies').findOne(
{_id: new ObjectId(id)}
);
}

export async function updateMovie({id, name, year}) {
return mongoClient.db(dbName)
.collection('movies')
.updateOne(
{_id: new ObjectId(id)},
{
$set: {name, year}
}
);
}


export async function deleteMovie(id) {
return mongoClient.db(dbName)
.collection('movies')
.deleteOne({_id: new ObjectId(id)});
}

In this code snippet, we created a mongodb client and implemented functions to interact with the movies collection.

Update the index.js file to use the db.js functions. The updated index.js file should look like this:

import express from 'express';
import bodyParser from 'body-parser';
import {createMovie, deleteMovie, getMovie, getMovies, updateMovie} from "./db.js";

const app = express();

app.use(bodyParser.json());

app.get('/', (req, res) => {
res.send(
`Welcome to Movies API. You can use this API to get and add movies at /movies.`
);
});

app.get('/movies', async (req, res) => {
const movies = await getMovies();
res.json(movies);
});

app.get('/movies/:id', async (req, res) => {
const id = req.params.id;
const movie = await getMovie(id);
res.json(movie);
})

app.post('/movies', async (req, res) => {
const {name, year} = req.body;
const id = await createMovie({name, year});
res.json({id})
});


app.put('/movies/:id', async (req, res) => {
const {name, year} = req.body;
const id = req.params.id;
await updateMovie({id, name, year});
res.end();
});

app.delete('/movies/:id', async (req, res) => {
const id = req.params.id;
await deleteMovie(id);
res.end();
});


const port = process.env.PORT || 8080;


app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

Run the app

You can run the app with the hereya run command:

hereya run node index.js

Now you can test the endpoints using a tool like curl or Postman or any http client. For example, with curl:

  • Get all movies:
curl http://localhost:8080/movies

# []
  • Create a new movie:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Inception", "year": 2010}' http://localhost:8080/movies

# {"id":"6662dfd7491f79189f82b0b6"} # The id will be different
  • Get a movie by id:
curl http://localhost:8080/movies/<id> # Replace <id> with the id returned in the previous step

# {"_id":"6662dfd7491f79189f82b0b6","name":"Inception","year":2010} # The id will be different
  • Update a movie:
curl -X PUT -H "Content-Type: application/json" -d '{"name": "Inception", "year": 2010}' http://localhost:8080/movies/<id> # Replace <id> with the id returned in the previous step
  • Delete a movie:
curl -X DELETE http://localhost:8080/movies/<id> # Replace <id> with the id returned in the previous step

Deploy the app to AWS

Warning: In this step, you will create actual resources in your AWS account. These resources may incur costs. It should not cost more than a few dollars, but make sure to clean up the resources after you finish the tutorial by reading the clean up section.

Now that you have developed your app, you can deploy it to AWS with a single command.

First of all, we need to bootstrap the AWS environment. This step is required only once. It creates the necessary resources for hereya to manage deployments in your AWS account. This step requires admin access to your AWS account. Make sure AWS CLI is installed and configured with your AWS account credentials.

  • Run the following command to bootstrap your AWS environment. It invokes the hereya/bootstrap-aws-stack package, which uses the AWS CDK to set up an S3 bucket and a DynamoDB table for managing openTofu package states. Both AWS CDK and openTofu are infrastructure-as-code frameworks used by Hereya packages.
hereya bootstrap aws
  • Create a workspace named staging:
hereya workspace create staging
  • Add a deployment package to your project. In this tutorial, we will deploy to AWS AppRunner using the package hereya/aws-apprunner-deploy:
hereya add hereya/aws-apprunner-deploy

A docker image will be built and deployed to AWS. Create a Dockerfile in the root of your project with the following content:

# Use the official Node.js image as the base image
FROM --platform=linux/amd64 node:20

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json files to the working directory
COPY package*.json ./

# Install dependencies
RUN npm install --only=production

# Copy the rest of the application code to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 8080

# Command to run the app
CMD ["node", "index.js"]

  • Deploy to AWS:
hereya deploy -w staging

This command deploys your application to AWS AppRunner, using AWS DocumentDB for the MongoDB-compatible database. Because this is the first time you’re running the deployment, it may take a few minutes to complete. Once the process finishes, you’ll receive a URL for your application.

You can then use this AWS AppRunner-provided URL to test your app’s endpoints.

Clean up

To avoid incurring costs, make sure to clean up the resources created in your AWS account.

  • Undeploy the app:
hereya undeploy -w staging
  • If you no longer want to use hereya in your AWS account, you can remove the resources created by hereya bootstrap aws by running:
hereya unbootstrap aws