COMP 305: Object-Oriented Software Design

University of San Diego, Fall 2025

CRUD Architecture

CRUD represents the four fundamental operations performed on persistent data: Create, Read, Update, and Delete. Most applications, especially web applications, are built around these core operations. Understanding CRUD is essential because it forms the foundation for data management across databases, APIs, and user interfaces.

The Four Operations

Create - Adding New Data

The Create operation adds new records or entities to a data store. This is typically the first operation users perform when starting to use an application.

// Database Create
async function createUser(userData) {
    const newUser = {
        id: generateId(),
        name: userData.name,
        email: userData.email,
        createdAt: new Date(),
        updatedAt: new Date()
    };

    // SQL Database
    const result = await db.query(
        'INSERT INTO users (id, name, email, created_at, updated_at) VALUES (?, ?, ?, ?, ?)',
        [newUser.id, newUser.name, newUser.email, newUser.createdAt, newUser.updatedAt]
    );

    return newUser;
}

// Array Create (in-memory)
const users = [];
function createUserInMemory(userData) {
    const newUser = {
        id: users.length + 1,
        ...userData,
        createdAt: new Date()
    };
    users.push(newUser);
    return newUser;
}

// LocalStorage Create
function createUserInStorage(userData) {
    const users = JSON.parse(localStorage.getItem('users') || '[]');
    const newUser = {
        id: Date.now(),
        ...userData
    };
    users.push(newUser);
    localStorage.setItem('users', JSON.stringify(users));
    return newUser;
}

Read - Retrieving Existing Data

The Read operation retrieves data from storage. This is the most frequently used operation, as users often need to view data before modifying or deleting it.

// Read All
async function getAllUsers() {
    const result = await db.query('SELECT * FROM users ORDER BY created_at DESC');
    return result.rows;
}

// Read One by ID
async function getUserById(id) {
    const result = await db.query('SELECT * FROM users WHERE id = ?', [id]);
    return result.rows[0];
}

// Read with Filtering
async function getUsersByRole(role) {
    const result = await db.query('SELECT * FROM users WHERE role = ?', [role]);
    return result.rows;
}

// Read with Search
async function searchUsers(searchTerm) {
    const result = await db.query(
        'SELECT * FROM users WHERE name LIKE ? OR email LIKE ?',
        [`%${searchTerm}%`, `%${searchTerm}%`]
    );
    return result.rows;
}

// Read with Pagination
async function getUsersPaginated(page = 1, pageSize = 10) {
    const offset = (page - 1) * pageSize;
    const result = await db.query(
        'SELECT * FROM users LIMIT ? OFFSET ?',
        [pageSize, offset]
    );
    return result.rows;
}

Update - Modifying Existing Data

The Update operation changes existing records. Updates can be partial (only some fields) or complete (replacing the entire record).

// Full Update (Replace)
async function updateUser(id, userData) {
    const result = await db.query(
        'UPDATE users SET name = ?, email = ?, updated_at = ? WHERE id = ?',
        [userData.name, userData.email, new Date(), id]
    );

    if (result.affectedRows === 0) {
        throw new Error('User not found');
    }

    return getUserById(id);
}

// Partial Update (Merge)
async function patchUser(id, partialData) {
    // Get existing user
    const existingUser = await getUserById(id);
    if (!existingUser) {
        throw new Error('User not found');
    }

    // Merge changes
    const updatedUser = {
        ...existingUser,
        ...partialData,
        updatedAt: new Date()
    };

    // Build dynamic UPDATE query
    const fields = Object.keys(partialData);
    const setClause = fields.map(field => `${field} = ?`).join(', ');
    const values = [...fields.map(f => partialData[f]), id];

    await db.query(
        `UPDATE users SET ${setClause}, updated_at = ? WHERE id = ?`,
        [...values, new Date(), id]
    );

    return updatedUser;
}

// Update with Validation
async function updateUserEmail(id, newEmail) {
    // Validate email format
    if (!isValidEmail(newEmail)) {
        throw new Error('Invalid email format');
    }

    // Check if email is already in use
    const existingUser = await db.query(
        'SELECT id FROM users WHERE email = ? AND id != ?',
        [newEmail, id]
    );

    if (existingUser.rows.length > 0) {
        throw new Error('Email already in use');
    }

    // Perform update
    await db.query(
        'UPDATE users SET email = ?, updated_at = ? WHERE id = ?',
        [newEmail, new Date(), id]
    );

    return getUserById(id);
}

Delete - Removing Data

The Delete operation removes records from storage. Deletes can be hard (permanent) or soft (marked as deleted but retained in the database).

// Hard Delete (Permanent)
async function deleteUser(id) {
    const result = await db.query('DELETE FROM users WHERE id = ?', [id]);

    if (result.affectedRows === 0) {
        throw new Error('User not found');
    }

    return { success: true, message: 'User deleted' };
}

// Soft Delete (Mark as deleted)
async function softDeleteUser(id) {
    const result = await db.query(
        'UPDATE users SET deleted_at = ?, is_active = false WHERE id = ?',
        [new Date(), id]
    );

    if (result.affectedRows === 0) {
        throw new Error('User not found');
    }

    return { success: true, message: 'User deactivated' };
}

// Delete with Cascade (Remove related data)
async function deleteUserWithPosts(userId) {
    // Start transaction
    await db.query('START TRANSACTION');

    try {
        // Delete user's posts
        await db.query('DELETE FROM posts WHERE user_id = ?', [userId]);

        // Delete user's comments
        await db.query('DELETE FROM comments WHERE user_id = ?', [userId]);

        // Delete user
        await db.query('DELETE FROM users WHERE id = ?', [userId]);

        // Commit transaction
        await db.query('COMMIT');

        return { success: true, message: 'User and related data deleted' };
    } catch (error) {
        // Rollback on error
        await db.query('ROLLBACK');
        throw error;
    }
}

// Bulk Delete
async function deleteInactiveUsers(daysInactive = 365) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysInactive);

    const result = await db.query(
        'DELETE FROM users WHERE last_login < ? AND is_active = false',
        [cutoffDate]
    );

    return {
        success: true,
        deletedCount: result.affectedRows
    };
}

CRUD in Web Applications

HTML Forms for CRUD

Traditional web applications use HTML forms to perform CRUD operations. Each operation typically has its own form or page.

<!-- Create Form -->
<form action="/users" method="POST">
    <h2>Create New User</h2>
    <input type="text" name="name" placeholder="Name" required>
    <input type="email" name="email" placeholder="Email" required>
    <button type="submit">Create User</button>
</form>

<!-- Read (Display) -->
<div class="user-list">
    <h2>Users</h2>
    <!-- This would be populated by server-side rendering -->
    <div class="user-card">
        <h3>Alice Johnson</h3>
        <p>alice@example.com</p>
        <a href="/users/1">View Details</a>
        <a href="/users/1/edit">Edit</a>
        <form action="/users/1" method="POST" style="display: inline;">
            <input type="hidden" name="_method" value="DELETE">
            <button type="submit">Delete</button>
        </form>
    </div>
</div>

<!-- Update Form -->
<form action="/users/1" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <h2>Edit User</h2>
    <input type="text" name="name" value="Alice Johnson" required>
    <input type="email" name="email" value="alice@example.com" required>
    <button type="submit">Update User</button>
</form>

<!-- Delete Confirmation -->
<form action="/users/1" method="POST">
    <input type="hidden" name="_method" value="DELETE">
    <h2>Delete User</h2>
    <p>Are you sure you want to delete Alice Johnson?</p>
    <button type="submit">Confirm Delete</button>
    <a href="/users">Cancel</a>
</form>

Server-Side CRUD Routes

Web servers expose CRUD operations through routes. While traditional forms only support GET and POST, servers often use method overrides or accept different HTTP methods.

// Express.js CRUD Routes
const express = require('express');
const app = express();

// CREATE - Handle form submission
app.post('/users', async (req, res) => {
    try {
        const newUser = await createUser(req.body);
        res.redirect(`/users/${newUser.id}`);
    } catch (error) {
        res.status(400).send(error.message);
    }
});

// READ - List all users
app.get('/users', async (req, res) => {
    const users = await getAllUsers();
    res.render('users/index', { users });
});

// READ - Get single user
app.get('/users/:id', async (req, res) => {
    const user = await getUserById(req.params.id);
    if (!user) {
        return res.status(404).send('User not found');
    }
    res.render('users/show', { user });
});

// UPDATE - Show edit form
app.get('/users/:id/edit', async (req, res) => {
    const user = await getUserById(req.params.id);
    if (!user) {
        return res.status(404).send('User not found');
    }
    res.render('users/edit', { user });
});

// UPDATE - Handle form submission
app.post('/users/:id', async (req, res) => {
    if (req.body._method === 'PUT') {
        try {
            await updateUser(req.params.id, req.body);
            res.redirect(`/users/${req.params.id}`);
        } catch (error) {
            res.status(400).send(error.message);
        }
    }
});

// DELETE - Handle deletion
app.post('/users/:id', async (req, res) => {
    if (req.body._method === 'DELETE') {
        try {
            await deleteUser(req.params.id);
            res.redirect('/users');
        } catch (error) {
            res.status(400).send(error.message);
        }
    }
});

Client-Side CRUD with JavaScript

Modern single-page applications perform CRUD operations using JavaScript and AJAX, providing a more dynamic user experience without page reloads.

// CRUD Manager Component
class UserManager {
    constructor(apiUrl) {
        this.apiUrl = apiUrl;
    }

    // CREATE
    async create(userData) {
        const response = await fetch(this.apiUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(userData)
        });

        if (!response.ok) {
            throw new Error('Failed to create user');
        }

        const newUser = await response.json();
        this.renderUser(newUser);
        return newUser;
    }

    // READ (all)
    async readAll() {
        const response = await fetch(this.apiUrl);
        const users = await response.json();
        this.renderUserList(users);
        return users;
    }

    // READ (one)
    async readOne(id) {
        const response = await fetch(`${this.apiUrl}/${id}`);
        if (!response.ok) {
            throw new Error('User not found');
        }
        return response.json();
    }

    // UPDATE
    async update(id, userData) {
        const response = await fetch(`${this.apiUrl}/${id}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(userData)
        });

        if (!response.ok) {
            throw new Error('Failed to update user');
        }

        const updatedUser = await response.json();
        this.renderUser(updatedUser);
        return updatedUser;
    }

    // DELETE
    async delete(id) {
        const confirmed = confirm('Are you sure you want to delete this user?');
        if (!confirmed) return;

        const response = await fetch(`${this.apiUrl}/${id}`, {
            method: 'DELETE'
        });

        if (!response.ok) {
            throw new Error('Failed to delete user');
        }

        this.removeUserFromUI(id);
        return { success: true };
    }

    renderUserList(users) {
        const container = document.getElementById('user-list');
        container.innerHTML = users.map(user => `
            <div class="user-card" data-id="${user.id}">
                <h3>${user.name}</h3>
                <p>${user.email}</p>
                <button onclick="userManager.edit(${user.id})">Edit</button>
                <button onclick="userManager.delete(${user.id})">Delete</button>
            </div>
        `).join('');
    }

    renderUser(user) {
        // Update or add user in the UI
    }

    removeUserFromUI(id) {
        const element = document.querySelector(`[data-id="${id}"]`);
        if (element) {
            element.remove();
        }
    }
}

// Usage
const userManager = new UserManager('/api/users');

// Create new user
document.getElementById('create-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    await userManager.create(Object.fromEntries(formData));
    e.target.reset();
});

// Load users on page load
userManager.readAll();

CRUD and REST

CRUD operations map directly to HTTP methods in RESTful APIs. This standardized mapping makes web APIs predictable and easy to understand.

CRUD Operation HTTP Method URL Pattern Example Response
Create POST /resources POST /users 201 Created + new resource
Read (all) GET /resources GET /users 200 OK + array of resources
Read (one) GET /resources/:id GET /users/123 200 OK + resource
Update (full) PUT /resources/:id PUT /users/123 200 OK + updated resource
Update (partial) PATCH /resources/:id PATCH /users/123 200 OK + updated resource
Delete DELETE /resources/:id DELETE /users/123 204 No Content

See the REST Architecture page for more details on how REST implements CRUD over HTTP.

Benefits of CRUD

Challenges and Considerations

Beyond Simple CRUD

While CRUD is fundamental, real-world applications often need additional operations:

// Extended CRUD operations
class ExtendedUserManager {
    // Standard CRUD
    async create(data) { /* ... */ }
    async read(id) { /* ... */ }
    async update(id, data) { /* ... */ }
    async delete(id) { /* ... */ }

    // Additional operations
    async archive(id) {
        // Not quite delete, not quite update
        await db.query(
            'UPDATE users SET status = ?, archived_at = ? WHERE id = ?',
            ['archived', new Date(), id]
        );
    }

    async restore(id) {
        // Undo soft delete or archive
        await db.query(
            'UPDATE users SET status = ?, archived_at = NULL WHERE id = ?',
            ['active', id]
        );
    }

    async activate(id) {
        // Business-specific state change
        await db.query(
            'UPDATE users SET is_active = ?, activated_at = ? WHERE id = ?',
            [true, new Date(), id]
        );
    }

    async search(criteria) {
        // More complex read operation
        const { name, email, role, minAge, maxAge } = criteria;
        // Build dynamic query
    }

    async bulkUpdate(ids, data) {
        // Update multiple records at once
        await db.query(
            'UPDATE users SET ? WHERE id IN (?)',
            [data, ids]
        );
    }
}

Key Takeaways

See Also