DEV Community

Aisalkyn Aidarova
Aisalkyn Aidarova

Posted on

lab: how frontend and backend communicate

Build a small user application:

  • Browser opens frontend from S3 static website
  • Frontend sends HTTP requests to backend on EC2
  • Backend returns JSON
  • You test API with Postman
  • You automate tests with Newman

Architecture

User Browser
    |
    v
S3 Static Frontend
    |
    | HTTP request
    v
EC2 Backend API (Node.js / Express)
    |
    v
JSON Response
Enter fullscreen mode Exit fullscreen mode

What You Will Learn

API

An API is a way for one system to talk to another.

Example:

  • frontend asks backend: “give me users”
  • backend sends JSON back

HTTP

HTTP is the communication protocol used between:

  • browser and server
  • frontend and backend
  • Postman and backend
  • Newman and backend

Methods

  • GET = read data
  • POST = create data
  • PUT = update data
  • DELETE = remove data

These methods exist so systems can clearly say what action they want.


Final Project Structure

aws-api-lab/
├── backend/
│   ├── server.js
│   ├── package.json
│   └── Dockerfile
├── frontend/
│   └── index.html
├── postman/
│   └── collection.json
└── README-commands.txt
Enter fullscreen mode Exit fullscreen mode

Part 1 — Backend API

1. Create backend/package.json

{
  "name": "aws-api-lab",
  "version": "1.0.0",
  "description": "Simple API lab for DevOps engineers",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.19.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Create backend/server.js

const express = require("express");
const cors = require("cors");

const app = express();
const PORT = 3000;

app.use(cors());
app.use(express.json());

let users = [
  { id: 1, name: "Aisalkyn" },
  { id: 2, name: "DevOps Student" }
];

// Health check
app.get("/health", (req, res) => {
  res.status(200).json({ status: "ok" });
});

// GET - Read all users
app.get("/users", (req, res) => {
  res.status(200).json(users);
});

// GET - Read one user
app.get("/users/:id", (req, res) => {
  const userId = Number(req.params.id);
  const user = users.find(u => u.id === userId);

  if (!user) {
    return res.status(404).json({ message: "User not found" });
  }

  res.status(200).json(user);
});

// POST - Create user
app.post("/users", (req, res) => {
  const { name } = req.body;

  if (!name || typeof name !== "string" || !name.trim()) {
    return res.status(400).json({ message: "Name is required" });
  }

  const newUser = {
    id: users.length ? users[users.length - 1].id + 1 : 1,
    name: name.trim()
  };

  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT - Update user
app.put("/users/:id", (req, res) => {
  const userId = Number(req.params.id);
  const { name } = req.body;

  const user = users.find(u => u.id === userId);

  if (!user) {
    return res.status(404).json({ message: "User not found" });
  }

  if (!name || typeof name !== "string" || !name.trim()) {
    return res.status(400).json({ message: "Name is required" });
  }

  user.name = name.trim();
  res.status(200).json(user);
});

// DELETE - Remove user
app.delete("/users/:id", (req, res) => {
  const userId = Number(req.params.id);
  const existingLength = users.length;

  users = users.filter(u => u.id !== userId);

  if (users.length === existingLength) {
    return res.status(404).json({ message: "User not found" });
  }

  res.status(200).json({ message: "User deleted" });
});

app.listen(PORT, "0.0.0.0", () => {
  console.log(`Server is running on http://0.0.0.0:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

This code is simple and working. It includes:

  • validation
  • proper status codes
  • health endpoint
  • all major HTTP methods

3. Create backend/Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package.json ./
RUN npm install

COPY server.js ./

EXPOSE 3000

CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Part 2 — Frontend

Create frontend/index.html

Important: later you will replace YOUR-EC2-PUBLIC-IP with your real EC2 public IP.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>AWS API Lab</title>
</head>
<body>
  <h1>AWS API Lab</h1>

  <p>This frontend is hosted on S3 and talks to backend on EC2.</p>

  <button onclick="getUsers()">Get Users</button>
  <button onclick="createUser()">Create User</button>

  <h2>Users</h2>
  <pre id="output"></pre>

  <script>
    const API_BASE_URL = "http://YOUR-EC2-PUBLIC-IP:3000";

    async function getUsers() {
      try {
        const response = await fetch(`${API_BASE_URL}/users`);
        const data = await response.json();
        document.getElementById("output").textContent = JSON.stringify(data, null, 2);
      } catch (error) {
        document.getElementById("output").textContent = "Error: " + error.message;
      }
    }

    async function createUser() {
      try {
        const response = await fetch(`${API_BASE_URL}/users`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: JSON.stringify({ name: "New AWS User" })
        });

        const data = await response.json();
        document.getElementById("output").textContent = JSON.stringify(data, null, 2);
      } catch (error) {
        document.getElementById("output").textContent = "Error: " + error.message;
      }
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Part 3 — Postman Collection

Create postman/collection.json

Replace YOUR-EC2-PUBLIC-IP before running.

{
  "info": {
    "name": "AWS API Lab Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Health Check",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "http://YOUR-EC2-PUBLIC-IP:3000/health",
          "protocol": "http",
          "host": ["YOUR-EC2-PUBLIC-IP"],
          "port": "3000",
          "path": ["health"]
        }
      }
    },
    {
      "name": "Get Users",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "http://YOUR-EC2-PUBLIC-IP:3000/users",
          "protocol": "http",
          "host": ["YOUR-EC2-PUBLIC-IP"],
          "port": "3000",
          "path": ["users"]
        }
      }
    },
    {
      "name": "Create User",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Postman User\"\n}"
        },
        "url": {
          "raw": "http://YOUR-EC2-PUBLIC-IP:3000/users",
          "protocol": "http",
          "host": ["YOUR-EC2-PUBLIC-IP"],
          "port": "3000",
          "path": ["users"]
        }
      }
    },
    {
      "name": "Update User",
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Updated User\"\n}"
        },
        "url": {
          "raw": "http://YOUR-EC2-PUBLIC-IP:3000/users/1",
          "protocol": "http",
          "host": ["YOUR-EC2-PUBLIC-IP"],
          "port": "3000",
          "path": ["users", "1"]
        }
      }
    },
    {
      "name": "Delete User",
      "request": {
        "method": "DELETE",
        "header": [],
        "url": {
          "raw": "http://YOUR-EC2-PUBLIC-IP:3000/users/2",
          "protocol": "http",
          "host": ["YOUR-EC2-PUBLIC-IP"],
          "port": "3000",
          "path": ["users", "2"]
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Part 4 — AWS Setup

Step 1: Launch EC2

Create one EC2 instance:

  • Ubuntu
  • t2.micro or t3.micro
  • allow:

    • SSH 22 from your IP
    • Custom TCP 3000 from anywhere for this lab
    • or HTTP 80 if you later use Nginx reverse proxy

For this simple lab, open:

  • port 22
  • port 3000

Step 2: Connect to EC2

ssh -i your-key.pem ubuntu@YOUR-EC2-PUBLIC-IP
Enter fullscreen mode Exit fullscreen mode

Step 3: Install Docker on EC2

sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker
sudo systemctl start docker
sudo usermod -aG docker ubuntu
newgrp docker
docker --version
Enter fullscreen mode Exit fullscreen mode

Step 4: Upload backend files to EC2

From your local machine:

scp -i your-key.pem -r backend ubuntu@YOUR-EC2-PUBLIC-IP:/home/ubuntu/
Enter fullscreen mode Exit fullscreen mode

Step 5: Build and run backend on EC2

SSH into EC2 and run:

cd /home/ubuntu/backend
docker build -t aws-api-lab .
docker run -d --name api -p 3000:3000 aws-api-lab
Enter fullscreen mode Exit fullscreen mode

Check:

docker ps
curl http://localhost:3000/health
curl http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

From your own laptop, also check:

curl http://YOUR-EC2-PUBLIC-IP:3000/health
Enter fullscreen mode Exit fullscreen mode

You should get:

{"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

Part 5 — Host Frontend on S3

Step 1: Create S3 bucket

Create an S3 bucket with a globally unique name, for example:

aisalkyn-aws-api-lab-frontend
Enter fullscreen mode Exit fullscreen mode

Step 2: Enable static website hosting

In S3 bucket:

  • go to Properties
  • enable Static website hosting
  • index document: index.html

Step 3: Upload frontend/index.html

Before uploading, replace:

http://YOUR-EC2-PUBLIC-IP:3000
Enter fullscreen mode Exit fullscreen mode

with your real IP.

Then upload the file.


Step 4: Make objects public

For a simple lab:

  • disable block public access for this bucket
  • allow read access to objects

Sample bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForWebsite",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Replace YOUR-BUCKET-NAME.


Step 5: Open S3 website URL

Now open the static website endpoint from S3.

When you click:

  • Get Users → frontend sends GET request to EC2 backend
  • Create User → frontend sends POST request to EC2 backend

That is the full frontend-to-backend flow.


Part 6 — Test with Postman

In Postman, use your EC2 public IP.

Health

GET http://YOUR-EC2-PUBLIC-IP:3000/health
Enter fullscreen mode Exit fullscreen mode

Get all users

GET http://YOUR-EC2-PUBLIC-IP:3000/users
Enter fullscreen mode Exit fullscreen mode

Create

POST http://YOUR-EC2-PUBLIC-IP:3000/users
Content-Type: application/json

{
  "name": "Aisalkyn API Student"
}
Enter fullscreen mode Exit fullscreen mode

Update

PUT http://YOUR-EC2-PUBLIC-IP:3000/users/1
Content-Type: application/json

{
  "name": "Updated Name"
}
Enter fullscreen mode Exit fullscreen mode

Delete

DELETE http://YOUR-EC2-PUBLIC-IP:3000/users/1
Enter fullscreen mode Exit fullscreen mode

Part 7 — Run with Newman

Install Newman on your laptop:

npm install -g newman
Enter fullscreen mode Exit fullscreen mode

Run:

cd postman
newman run collection.json
Enter fullscreen mode Exit fullscreen mode

This is how DevOps engineers automate API checks.


Why Each HTTP Method Works

GET

Used to read information.
It does not usually change data.

Example:

GET /users
Enter fullscreen mode Exit fullscreen mode

POST

Used to create new data.

Example:

POST /users
Enter fullscreen mode Exit fullscreen mode

Body:

{
  "name": "New User"
}
Enter fullscreen mode Exit fullscreen mode

PUT

Used to update existing data.

Example:

PUT /users/1
Enter fullscreen mode Exit fullscreen mode

DELETE

Used to remove data.

Example:

DELETE /users/1
Enter fullscreen mode Exit fullscreen mode

These methods matter because backend code behaves differently depending on the request type.


Why HTTP Is There

HTTP is there because systems need a standard language for communication.

Without HTTP:

  • browser would not know how to ask server for data
  • Postman would not know how to test endpoint
  • frontend would not know how to send create/update/delete actions
  • backend would not know how to interpret request type

HTTP gives structure:

  • method
  • URL
  • headers
  • body
  • response code

Important DevOps Understanding

A DevOps engineer does not always write large applications, but must understand:

  • where frontend is hosted
  • where backend is running
  • how requests reach backend
  • what ports are open
  • what security groups allow traffic
  • how to test health and endpoints
  • how to automate checks with Newman
  • how to troubleshoot if app fails

Common Problems and Fixes

1. Frontend opens but cannot reach backend

Usually:

  • EC2 security group does not allow port 3000
  • wrong EC2 public IP in frontend file
  • backend container is not running

Check:

docker ps
curl http://localhost:3000/health
Enter fullscreen mode Exit fullscreen mode

2. Browser shows CORS error

You already included:

app.use(cors());
Enter fullscreen mode Exit fullscreen mode

That fixes most basic CORS problems for this lab.


3. Newman says request URL is empty

That usually means collection JSON does not contain full URL, or placeholder was not replaced.


4. Port 3000 not reachable

Check EC2 security group inbound rules.


Commands Summary

Local backend test

cd backend
npm install
node server.js
Enter fullscreen mode Exit fullscreen mode

Docker local

docker build -t aws-api-lab .
docker run -d -p 3000:3000 aws-api-lab
Enter fullscreen mode Exit fullscreen mode

EC2 Docker

cd /home/ubuntu/backend
docker build -t aws-api-lab .
docker run -d --name api -p 3000:3000 aws-api-lab
Enter fullscreen mode Exit fullscreen mode

Health check

curl http://YOUR-EC2-PUBLIC-IP:3000/health
Enter fullscreen mode Exit fullscreen mode

Newman

newman run collection.json
Enter fullscreen mode Exit fullscreen mode

Interview Value

This lab helps you answer questions like:

What is an API?

An API allows systems like frontend, Postman, or other services to communicate with backend using HTTP requests and responses.

Why do we use GET, POST, PUT, DELETE?

Because each method represents a different type of action on the resource: read, create, update, and delete.

What does a DevOps engineer check here?

  • backend availability
  • port accessibility
  • health endpoint
  • status codes
  • request/response correctness
  • frontend-to-backend connectivity
  • automated testing with Newman

Top comments (0)