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/users | List all users |
| GET | /api/users/1 | Get user #1 |
| POST | /api/users | Create user |
| PUT | /api/users/1 | Update user #1 |
| DELETE | /api/users/1 | Delete 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
- User Action: Click "Add Student" button
- Frontend: Sends POST request with student data
- Backend: Validates data, runs SQL INSERT
- Database: Stores the data, returns ID
- Backend: Sends success response as JSON
- 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
- Use environment variables for secrets
- Set NODE_ENV to 'production'
- Enable CORS for your frontend domain
- Use HTTPS (SSL certificate)
- Set up error logging
- Configure database connection pooling
- 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
- Push code to GitHub
- Connect repo to Render
- Set environment variables
- 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
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
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!