Skip to content

Chapter 4.6: Creating a Simple Interface for the API

Study time: 1 hour 15 minutes


1. Final Assembly: Launching the "Mission Control Center"

We have studied all the systems separately: configured the "antenna" (Fetch), learned to send "commands" (GET, POST, DELETE), and developed "emergency protocols" (error handling).

It's time to bring all the components together and launch our Mission Control — a full-fledged, interactive interface for managing the space fleet.

Our goal:

  • Create a single, clean, and understandable interface.
  • Implement the full CRUD cycle: creating, displaying, updating, and deleting ships.
  • Add visual feedback for the user (loading, success, error).

💡 Space Analogy:

We are moving from individual test consoles to the main screen of Mission Control. It must have all the necessary buttons and indicators so that a single operator can manage the entire fleet without switching between dozens of different systems.


2. Interface Design: The "Dashboard"

We will need more structured HTML. We will use "cards" to display the ships and a modal window for editing them.

Updating index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MCC v2.0 - Fleet Management</title>
    <link rel="stylesheet" href="style.css"> <!-- Include styles -->
</head>
<body>
    <header>
        <h1>Space Fleet Control Panel</h1>
    </header>

    <main>
        <section id="fleet-controls">
            <button id="load-fleet-btn">Refresh Fleet List</button>
            <button id="show-create-form-btn">Launch New Craft</button>
        </section>

        <section id="fleet-display">
            <h2>Current Fleet Composition</h2>
            <div id="fleet-list" class="cards-container">
                <!-- Ship cards will be here -->
            </div>
        </section>
    </main>

    <!-- Modal for creating/editing (initially hidden) -->
    <div id="modal" class="modal-overlay" style="display: none;">
        <div class="modal-content">
            <h2 id="modal-title">Launch New Craft</h2>
            <form id="ship-form">
                <input type="hidden" id="ship-id">
                <input type="text" id="ship-name" placeholder="Name" required>
                <input type="text" id="ship-type" placeholder="Type" required>
                <input type="number" id="ship-year" placeholder="Launch Year" required>
                <input type="text" id="ship-status" placeholder="Status" required>
                <div class="modal-actions">
                    <button type="submit" id="save-btn">Save</button>
                    <button type="button" id="cancel-btn">Cancel</button>
                </div>
            </form>
            <div id="notification-area"></div>
        </div>
    </div>

    <script src="api.js"></script>
    <script src="app.js"></script>
</body>
</html>


3. Adding a "Cosmic" Design: style.css

Create a style.css file to make our Mission Control look decent.

/* style.css */
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #1a1a2e;
    color: #e0e0e0;
    margin: 0;
    padding: 20px;
}
header { text-align: center; margin-bottom: 20px; }
button {
    background-color: #4a4e69;
    color: white;
    border: none;
    padding: 10px 15px;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s;
}
button:hover { background-color: #6a6e94; }
.cards-container {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
}
.card {
    background-color: #2a2a4e;
    border: 1px solid #4a4e69;
    border-radius: 8px;
    padding: 15px;
}
.card h3 { margin-top: 0; color: #9394a5; }
.card-actions { margin-top: 15px; }

/* Styles for the modal window */
.modal-overlay {
    position: fixed;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background-color: rgba(0,0,0,0.7);
    display: flex;
    justify-content: center;
    align-items: center;
}
.modal-content {
    background: #1a1a2e;
    padding: 20px;
    border-radius: 8px;
    border: 1px solid #4a4e69;
    width: 90%;
    max-width: 500px;
}
#ship-form input {
    width: calc(100% - 20px);
    padding: 10px;
    margin-bottom: 10px;
    border-radius: 4px;
    border: 1px solid #4a4e69;
    background-color: #2a2a4e;
    color: white;
}
.modal-actions { text-align: right; }


4. Complete Logic Overhaul: app.js

Now we will write the final version of app.js, combining all our knowledge.

// app.js

// --- DOM Elements ---
const loadFleetBtn = document.getElementById('load-fleet-btn');
const fleetListContainer = document.getElementById('fleet-list');
const modal = document.getElementById('modal');
const modalTitle = document.getElementById('modal-title');
const shipForm = document.getElementById('ship-form');
const saveBtn = document.getElementById('save-btn');
const cancelBtn = document.getElementById('cancel-btn');
const showCreateFormBtn = document.getElementById('show-create-form-btn');
const notificationArea = document.getElementById('notification-area');

// --- UI Functions ---

function showNotification(message, isError = false) {
    notificationArea.textContent = message;
    notificationArea.style.color = isError ? '#ff6b6b' : '#6bff6b';
}

function openModalForCreate() {
    shipForm.reset();
    document.getElementById('ship-id').value = '';
    modalTitle.textContent = 'Launch New Craft';
    modal.style.display = 'flex';
}

function openModalForEdit(ship) {
    shipForm.reset();
    document.getElementById('ship-id').value = ship.id;
    document.getElementById('ship-name').value = ship.name;
    document.getElementById('ship-type').value = ship.type;
    document.getElementById('ship-year').value = ship.launch_year;
    document.getElementById('ship-status').value = ship.status;
    modalTitle.textContent = `Editing: ${ship.name}`;
    modal.style.display = 'flex';
}

function closeModal() {
    modal.style.display = 'none';
    notificationArea.textContent = '';
}

function createShipCard(ship) {
    const card = document.createElement('div');
    card.className = 'card';
    card.innerHTML = `
        <h3>${ship.name} (ID: ${ship.id})</h3>
        <p>Type: ${ship.type}</p>
        <p>Launch Year: ${ship.launch_year}</p>
        <p>Status: ${ship.status}</p>
        <div class="card-actions">
            <button class="edit-btn" data-ship-id="${ship.id}">Edit</button>
            <button class="delete-btn" data-ship-id="${ship.id}">Decommission</button>
        </div>
    `;
    return card;
}

// --- API and Display Logic ---

async function fetchAndDisplayFleet() {
    try {
        fleetListContainer.innerHTML = '<p>Loading telemetry...</p>';
        const ships = await apiRequest('/spaceships');

        fleetListContainer.innerHTML = '';
        if (ships.length === 0) {
            fleetListContainer.innerHTML = '<p>No crafts in the registry.</p>';
            return;
        }
        ships.forEach(ship => {
            const card = createShipCard(ship);
            fleetListContainer.appendChild(card);
        });
    } catch (error) {
        fleetListContainer.innerHTML = `<p style="color: #ff6b6b;">Error loading fleet: ${error.message}</p>`;
    }
}

async function handleSaveShip(event) {
    event.preventDefault();
    const shipId = document.getElementById('ship-id').value;
    const shipData = {
        name: document.getElementById('ship-name').value,
        type: document.getElementById('ship-type').value,
        launch_year: parseInt(document.getElementById('ship-year').value),
        status: document.getElementById('ship-status').value
    };

    try {
        let response;
        if (shipId) {
            // Update (PUT)
            response = await apiRequest(`/spaceships/${shipId}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(shipData)
            });
            showNotification(`Craft "${response.name}" successfully updated!`);
        } else {
            // Create (POST)
            response = await apiRequest('/spaceships', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(shipData)
            });
            showNotification(`Craft "${response.name}" successfully launched! ID: ${response.id}`);
        }

        setTimeout(() => {
            closeModal();
            fetchAndDisplayFleet();
        }, 1500);

    } catch (error) {
        showNotification(error.message, true);
    }
}

async function handleDeleteShip(shipId) {
    if (!confirm(`Are you sure you want to decommission the craft with ID ${shipId}?`)) return;

    try {
        await apiRequest(`/spaceships/${shipId}`, { method: 'DELETE' });
        alert('Craft successfully decommissioned.');
        fetchAndDisplayFleet();
    } catch (error) {
        alert(`Error during decommissioning: ${error.message}`);
    }
}

// --- Event Handlers ---

document.addEventListener('DOMContentLoaded', fetchAndDisplayFleet);
loadFleetBtn.addEventListener('click', fetchAndDisplayFleet);
showCreateFormBtn.addEventListener('click', openModalForCreate);
cancelBtn.addEventListener('click', closeModal);
shipForm.addEventListener('submit', handleSaveShip);

fleetListContainer.addEventListener('click', async (event) => {
    const target = event.target;
    if (target.classList.contains('delete-btn')) {
        handleDeleteShip(target.dataset.shipId);
    }
    if (target.classList.contains('edit-btn')) {
        try {
            const ship = await apiRequest(`/spaceships/${target.dataset.shipId}`);
            openModalForEdit(ship);
        } catch (error) {
            alert(`Failed to load data for editing: ${error.message}`);
        }
    }
});

5. Final Tests

  1. Start the FastAPI server: uvicorn main:app --reload
  2. Open index.html in your browser (via Live Server).
  3. Check the full cycle:
    • The list of ships should load automatically.
    • Click "Launch New Craft", fill out the form, and save. Make sure the new ship appears in the list.
    • Click "Edit" on any ship, change the data, and save. Make sure the information is updated.
    • Click "Decommission" on any ship, confirm the action. Make sure it disappears from the list.
    • Check all error scenarios (invalid data, server shutdown).

Quiz to Reinforce

1. A modal window in a web interface is...

2. The `DOMContentLoaded` event occurs when...

3. Why do we use a single form for both creating and editing in the final version?

4. `data-ship-id="${ship.id}"` is an example of...

5. Code refactoring (e.g., moving logic to `api.js`) is needed to...


🚀 Chapter Summary

You have successfully completed the construction and launch of your "Mission Control Center".

  • 🖥️ You have created a structured and styled HTML/CSS interface.
  • ⚙️ You have written clean, modular JavaScript code that implements the full CRUD cycle.
  • 🛰️ Your frontend can now fully manage the backend created with FastAPI.

Congratulations on successfully completing Chapter 4! You have come all the way from sending a simple fetch request to creating a full-fledged web application that interacts with your own API.