Week 14: API Integration & Full-Stack

Unit V - Full-Stack Web Development

Connecting all the pieces together!

What You'll Learn

  • REST API fundamentals and design
  • Fetching data from APIs in the frontend
  • Full-stack architecture patterns
  • Deployment basics and best practices

The Restaurant Analogy (Part 2)

The API is the menu - it tells the frontend (customer) what they can order and how. The backend (kitchen) prepares the data (food) and sends it back through the API. The frontend then presents it beautifully to the user. Full-stack development means you build both the kitchen and the dining room!

Press → or click Next to continue

What is a REST API?

REST = Representational State Transfer

A set of rules for building web services:

  • Client-Server: Frontend and backend are separate
  • Stateless: Each request is independent
  • Cacheable: Responses can be cached
  • Uniform Interface: Consistent URL structure

HTTP Methods

GET Read data
POST Create data
PUT Update data
DELETE Remove data

API Endpoint Design

Method Endpoint Action
GET/api/usersList all users
GET/api/users/1Get user #1
POST/api/usersCreate user
PUT/api/users/1Update user #1
DELETE/api/users/1Delete user #1

JSON Response Format

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Alice",
    "email": "alice@mail.com"
  }
}

The Fetch API

Modern JavaScript method for making HTTP requests from the browser:

GET Request

// Basic GET request
async function getUsers() {
    try {
        const response = await fetch(
            'https://api.example.com/users'
        );

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }

        const data = await response.json();
        console.log(data);
        return data;

    } catch (error) {
        console.error('Fetch error:', error);
    }
}

// Call it
getUsers().then(users => {
    users.forEach(user => {
        console.log(user.name);
    });
});

POST Request

// POST request with data
async function createUser(userData) {
    try {
        const response = await fetch(
            'https://api.example.com/users',
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(userData)
            }
        );

        const result = await response.json();

        if (!response.ok) {
            throw new Error(result.error);
        }

        console.log('Created:', result);
        return result;

    } catch (error) {
        console.error('Error:', error);
    }
}

// Usage
createUser({
    name: 'Bob',
    email: 'bob@mail.com'
});

Fetch API - PUT & DELETE

PUT Request (Update)

async function updateUser(id, updates) {
    try {
        const response = await fetch(
            `https://api.example.com/users/${id}`,
            {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(updates)
            }
        );

        if (!response.ok) {
            throw new Error('Update failed');
        }

        const result = await response.json();
        console.log('Updated:', result);
        return result;

    } catch (error) {
        console.error('Error:', error);
    }
}

// Usage
updateUser(1, {
    name: 'Alice Updated',
    email: 'alice.new@mail.com'
});

DELETE Request

async function deleteUser(id) {
    try {
        const response = await fetch(
            `https://api.example.com/users/${id}`,
            {
                method: 'DELETE'
            }
        );

        if (!response.ok) {
            throw new Error('Delete failed');
        }

        console.log('User deleted');
        return true;

    } catch (error) {
        console.error('Error:', error);
        return false;
    }
}

// Usage
deleteUser(5).then(success => {
    if (success) {
        alert('User removed!');
    }
});

HTTP Status Codes

  • 200 - OK (success)
  • 201 - Created
  • 400 - Bad Request
  • 401 - Unauthorized
  • 404 - Not Found
  • 500 - Server Error

Full-Stack Architecture

Frontend

HTML, CSS, JS
Bootstrap
Browser

Backend API

Node.js
Express
REST API

Database

MySQL
Tables
Data Storage

How Data Flows

  1. User Action: Click "Add Student" button
  2. Frontend: Sends POST request with student data
  3. Backend: Validates data, runs SQL INSERT
  4. Database: Stores the data, returns ID
  5. Backend: Sends success response as JSON
  6. Frontend: Updates the UI with new student

Project Structure

student-app/
  |-- frontend/
  |     |-- index.html
  |     |-- style.css
  |     |-- app.js
  |-- backend/
  |     |-- server.js
  |     |-- config/
  |     |     |-- database.js
  |     |-- models/
  |     |     |-- Student.js
  |     |-- routes/
  |     |     |-- students.js
  |     |-- package.json
  |-- README.md

Frontend Connecting to Backend

Complete Frontend Example

<!-- index.html -->
<div class="container mt-4">
    <h1>Student Manager</h1>

    <!-- Add Student Form -->
    <form id="studentForm" class="mb-4">
        <div class="row g-3">
            <div class="col-md-4">
                <input type="text" id="name" class="form-control" placeholder="Name" required>
            </div>
            <div class="col-md-4">
                <input type="email" id="email" class="form-control" placeholder="Email" required>
            </div>
            <div class="col-md-2">
                <input type="number" id="age" class="form-control" placeholder="Age">
            </div>
            <div class="col-md-2">
                <button type="submit" class="btn btn-primary w-100">Add Student</button>
            </div>
        </div>
    </form>

    <!-- Student List -->
    <table class="table table-striped">
        <thead>
            <tr><th>ID</th><th>Name</th><th>Email</th><th>Age</th><th>Actions</th></tr>
        </thead>
        <tbody id="studentList"></tbody>
    </table>
</div>

Frontend JavaScript (app.js)

// app.js - Frontend API integration
const API_URL = 'http://localhost:3000/api/students';

// Load students on page load
document.addEventListener('DOMContentLoaded', loadStudents);
document.getElementById('studentForm').addEventListener('submit', addStudent);

// GET - Load all students
async function loadStudents() {
    try {
        const response = await fetch(API_URL);
        const result = await response.json();

        const tbody = document.getElementById('studentList');
        tbody.innerHTML = '';

        result.data.forEach(student => {
            tbody.innerHTML += `
                <tr>
                    <td>${student.id}</td>
                    <td>${student.name}</td>
                    <td>${student.email}</td>
                    <td>${student.age || '-'}</td>
                    <td>
                        <button onclick="deleteStudent(${student.id})" class="btn btn-danger btn-sm">Delete</button>
                    </td>
                </tr>`;
        });
    } catch (error) {
        console.error('Error loading students:', error);
    }
}

// POST - Add new student
async function addStudent(e) {
    e.preventDefault();
    const studentData = {
        name: document.getElementById('name').value,
        email: document.getElementById('email').value,
        age: document.getElementById('age').value || null
    };

    try {
        const response = await fetch(API_URL, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(studentData)
        });

        if (response.ok) {
            document.getElementById('studentForm').reset();
            loadStudents(); // Refresh the list
        }
    } catch (error) {
        console.error('Error adding student:', error);
    }
}

// DELETE - Remove student
async function deleteStudent(id) {
    if (!confirm('Are you sure?')) return;
    try {
        await fetch(`${API_URL}/${id}`, { method: 'DELETE' });
        loadStudents(); // Refresh the list
    } catch (error) {
        console.error('Error:', error);
    }
}

CORS - Cross-Origin Resource Sharing

What is CORS?

A security feature that blocks requests from different origins (domains).

The Problem

  • Frontend runs on localhost:5500
  • Backend runs on localhost:3000
  • Different ports = different origins
  • Browser blocks the request!

Common Error

Access to fetch at 'http://localhost:3000'
from origin 'http://localhost:5500'
has been blocked by CORS policy

The Solution

Add CORS middleware to your Express backend:

npm install cors
const express = require('express');
const cors = require('cors');
const app = express();

// Option 1: Allow all origins (development)
app.use(cors());

// Option 2: Specific origins (production)
app.use(cors({
    origin: [
        'http://localhost:5500',
        'https://mysite.com'
    ],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type']
}));

Alternative: Serve Frontend from Express

// Serve static files from frontend folder
app.use(express.static('public'));

// Now frontend and backend share the
// same origin - no CORS needed!

Error Handling & Loading States

Robust API Calls

// API helper with error handling
class ApiService {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;

        try {
            const response = await fetch(url, {
                headers: {
                    'Content-Type': 'application/json',
                    ...options.headers
                },
                ...options
            });

            const data = await response.json();

            if (!response.ok) {
                throw new Error(
                    data.error || 'Request failed'
                );
            }

            return { success: true, data };

        } catch (error) {
            return {
                success: false,
                error: error.message
            };
        }
    }

    get(endpoint) {
        return this.request(endpoint);
    }

    post(endpoint, body) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(body)
        });
    }
}

const api = new ApiService('http://localhost:3000');

Loading States in UI

async function loadStudents() {
    const container = document
        .getElementById('studentList');

    // Show loading state
    container.innerHTML = `
        <tr><td colspan="5"
            class="text-center">
            <div class="spinner-border"></div>
            <p>Loading students...</p>
        </td></tr>`;

    const result = await api.get('/api/students');

    if (!result.success) {
        // Show error state
        container.innerHTML = `
            <tr><td colspan="5"
                class="text-center text-danger">
                Error: ${result.error}
                <br>
                <button onclick="loadStudents()"
                    class="btn btn-sm btn-primary
                    mt-2">Retry</button>
            </td></tr>`;
        return;
    }

    if (result.data.length === 0) {
        // Show empty state
        container.innerHTML = `
            <tr><td colspan="5"
                class="text-center text-muted">
                No students found.
                Add one above!
            </td></tr>`;
        return;
    }

    // Show data
    renderStudents(result.data);
}

Consuming Third-Party APIs

Free Public APIs

  • JSONPlaceholder - Fake REST API for testing
  • OpenWeather - Weather data
  • GitHub API - Repository data
  • REST Countries - Country information

Example: Fetch GitHub User

async function getGitHubUser(username) {
    const response = await fetch(
        `https://api.github.com/users/${username}`
    );
    const user = await response.json();

    document.getElementById('profile').innerHTML = `
        <img src="${user.avatar_url}"
             width="100">
        <h3>${user.name}</h3>
        <p>${user.bio || 'No bio'}</p>
        <p>Repos: ${user.public_repos}</p>
        <p>Followers: ${user.followers}</p>
    `;
}

getGitHubUser('octocat');

Example: Weather App

// Using OpenWeather API (needs API key)
async function getWeather(city) {
    const API_KEY = 'your_api_key';
    const url = `https://api.openweathermap.org/
        data/2.5/weather?q=${city}
        &appid=${API_KEY}&units=metric`;

    const response = await fetch(url);
    const data = await response.json();

    return {
        city: data.name,
        temp: data.main.temp,
        description: data.weather[0].description,
        humidity: data.main.humidity
    };
}

// Example: REST Countries API
async function getCountryInfo(name) {
    const response = await fetch(
        `https://restcountries.com/v3.1/name/${name}`
    );
    const [country] = await response.json();

    return {
        name: country.name.common,
        capital: country.capital[0],
        population: country.population,
        flag: country.flags.svg
    };
}

API Keys

Many APIs require an API key. Never expose API keys in frontend code! Use your backend as a proxy to keep keys secret.

Deployment Basics

Where to Deploy?

Frontend (Static Sites)

  • GitHub Pages - Free, from your repo
  • Netlify - Free tier, CI/CD
  • Vercel - Free tier, great for Next.js

Backend (Node.js)

  • Render - Free tier, easy setup
  • Railway - Free tier, database included
  • DigitalOcean - $4/month VPS
  • AWS/GCP/Azure - Enterprise options

Database

  • PlanetScale - Free MySQL hosting
  • Supabase - Free PostgreSQL
  • MongoDB Atlas - Free NoSQL

Deployment Checklist

  1. Use environment variables for secrets
  2. Set NODE_ENV to 'production'
  3. Enable CORS for your frontend domain
  4. Use HTTPS (SSL certificate)
  5. Set up error logging
  6. Configure database connection pooling
  7. Add health check endpoint

Environment Variables

# Production .env
NODE_ENV=production
PORT=3000
DB_HOST=db.planetscale.io
DB_USER=app_user
DB_PASSWORD=super_secret
DB_NAME=myapp_prod

# Frontend URL (for CORS)
FRONTEND_URL=https://myapp.netlify.app

Quick Deploy with Render

  1. Push code to GitHub
  2. Connect repo to Render
  3. Set environment variables
  4. Deploy automatically!

CampusKart Milestone: Full-Stack & Deploy

Deliverable: Fully connected full-stack app deployed to Vercel with Neon Postgres + Stripe payments

What to Build

  • Frontend fetches from backend API (not local JSON)
  • Registration and product forms submit to API
  • Stripe Checkout integration for payments
  • CORS configured on backend
  • Deploy to Vercel (frontend + serverless API)
  • Neon Postgres as cloud database
  • PWA manifest for mobile install

The Payoff

"Open your phone. Open the URL. Your app is live. You built this from scratch over 12 weeks."

Share the deployed URL with classmates for testing!

Video Resources

REST API Concepts - Fireship

Frontend Masters

Full Stack for Front-End v3

MDN Fetch API

developer.mozilla.org

Complete Fetch API documentation

Public APIs List

github.com/public-apis

Curated list of free APIs to practice

Postman

postman.com

Tool for testing APIs

Practice Exercises

Exercise 1: Weather Dashboard

Build using a weather API:

  • Search by city name
  • Display temperature, humidity
  • Show weather icon
  • Loading and error states

Exercise 2: GitHub Profile Viewer

Using the GitHub API:

  • Search GitHub users
  • Show avatar and bio
  • List repositories
  • Show follower count

Exercise 3: Full-Stack Todo App

Build frontend + backend:

  • Express API with MySQL
  • Bootstrap frontend
  • Full CRUD operations
  • Mark as complete feature

Exercise 4: Student Manager

Complete full-stack app:

  • Registration and listing
  • Search and filter
  • Edit and delete
  • Deploy to Render + Netlify

Examples to Study

View Week 14 Examples →

Week 14 Summary

Key Concepts

  • REST APIs use HTTP methods (GET, POST, PUT, DELETE)
  • Fetch API for making HTTP requests in JavaScript
  • JSON is the standard data format for APIs
  • CORS enables cross-origin requests
  • Full-stack = Frontend + Backend + Database
  • Handle loading, error, and empty states

Best Practices

  • Use async/await for API calls
  • Always handle errors gracefully
  • Show loading states to users
  • Keep API keys on the backend
  • Validate data on both frontend and backend
  • Use HTTPS in production

Coming Next Week

Week 15: Case Study & Review - Full project walkthrough and course summary!