How to fix the “preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header” warning on my express app?

I’m making a second hand bookstore website that uses Aiven as the database and Vercel to deploy. It’s rather simple because it’s a school project, and it uses vanilla JS. I am trying to fetch some data from some APIs but they don’t work at all.

Here’s my server.js

const express = require('express');
const mysql = require('mysql2');
const path = require('path');
const cors = require('cors');
const fs = require('fs');

const fetch = (...args) =>
    import("node-fetch").then(({ default: fetch }) => fetch(...args));

const app = express();
const PORT = process.env.PORT || 3001;

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

const corsOptions = {
    origin: 'http://localhost:3001', // Your frontend origin
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
};

app.use(cors(corsOptions));
require('dotenv').config();

const connection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
    ssl: {
        rejectUnauthorized: false,
        ca: fs.readFileSync(process.env.SSL_CA),
    },
});

console.log('Connecting to database:', {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    database: process.env.DB_NAME,
    port: process.env.DB_PORT,
});

connection.connect(err => {
    if (err) {
        console.error('Database connection error:', err);
        return;
    }
    console.log('Connected to the database');
});

app.get('/test-db', (req, res) => {
    connection.query('SELECT 1', (err, results) => {
        if (err) {
            return res.status(500).json({ error: 'Database connection error' });
        }
        res.json({ message: 'Database is connected', results });
    });
});

app.get('/books', (req, res) => {
    const query = 'SELECT * FROM books';

    connection.query(query, (err, results) => {
        if (err) {
            return res.status(500).json({ error: 'Error fetching books' });
        }
        res.json(results);
    });
});


console.log('Database Host:', process.env.DB_HOST);
console.log('Database Name:', process.env.DB_NAME);

connection.connect(err => {
    if (err) {
        console.error('Database connection error:', err);
        return;
    }
    console.log('Connected to the database');
});

app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

console.log('Using SSL CA file:', process.env.SSL_CA);
console.log('Reading CA:', fs.readFileSync(process.env.SSL_CA).toString());

app.use((req, res, next) => {
    console.log(`Incoming Request: ${req.method} ${req.url}`);
    console.log('Request Headers:', req.headers);
    next();
});

and here’s the html file where the books are fetched

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Inventory</title>
    <link rel="stylesheet" href="istyle.css">
</head>
<body>
    <div id="navbar">
        <img src="images/BookbackICON.png" alt="">
        <label class="hamburger-menu">
            <input type="checkbox" />
        </label>
        <aside class="sidebar">
            <nav>
                <a href="index.html">Home</a>
                <a href="inventory.html">Inventory</a>
            </nav>
        </aside>
    </div>
    <section id="sec1">
        <span id="search">
            <input type="text" id="search-input" placeholder="Search for a book by name">
            <button id="search-button">Search</button>
        </span>
        <ul id="book-list"></ul>
    </section>

    <script>
        
        let books = [];  
        let uniqueBooks = [];  
        let duplicateCounts = {};

        function deduplicateBooks(data) {
    const uniqueBooksMap = {};
    duplicateCounts = {};

    data.forEach(book => {
        const isbnFull = book.id.split('-')[0]; // Use the full ISBN without trimming the last 5 digits
        const grade = book.grade; // Get the grade

        const uniqueKey = `${isbnFull}-${grade}`;


        if (duplicateCounts[uniqueKey]) {
            duplicateCounts[uniqueKey]++;
        } else {
            duplicateCounts[uniqueKey] = 1;
            uniqueBooksMap[uniqueKey] = book; // Store unique book information
        }
    });


    return Object.values(uniqueBooksMap);
}

function fetchBooks() {
fetch('https://bookback-i1o4juwqu-mohammed-aayan-pathans-projects.vercel.app/books', {
    method: 'GET', // Change as needed
    headers: {
        'Content-Type': 'application/json', // Change as needed
        // Any other headers you might need
    },
    credentials: 'include', // Include this if your API requires credentials
})
.then(response => {
    if (!response.ok) {
        throw new Error('Network response was not ok: ' + response.statusText);
    }
    return response.json();
})
.then(data => {
    console.log('Fetched books:', data);
})
.catch(error => {
    console.error('Fetch error:', error);
});
}


        function displayBooks(booksToDisplay) {
            const bookList = document.getElementById('book-list');
            bookList.innerHTML = ''; // Clear the current list


            const listItems = [];

            booksToDisplay.forEach(book => {
    const isbn = book.id.split('-')[0]; // Extract full ISBN part
    const li = document.createElement('li');
    li.style.cursor = 'pointer'; // Change cursor to pointer for better UX

    fetch(`https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}`)
        .then(response => response.json())
        .then(bookData => {
            const img = document.createElement('img');
            img.style.borderRadius = '4px';
            const title = book.name || 'Title not available';

            // Handle Google Books data
            if (bookData.items && bookData.items.length > 0) {
                const bookInfo = bookData.items[0].volumeInfo;
                const imageLinks = bookInfo.imageLinks;
                const coverImage = imageLinks?.large || imageLinks?.medium || imageLinks?.thumbnail || 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/No_image_available.svg/1200px-No_image_available.svg.png';
                img.src = coverImage;
            } else {
                img.src = `https://images-na.ssl-images-amazon.com/images/P/${isbn}.L.jpg`;
            }

            img.alt = title;
            img.style.maxWidth = '200px';  // Adjust the image size as needed

            // Handle price safely (fallback if not a number or missing)
            let priceDisplay = 'Price not available';
            if (book.price && !isNaN(book.price)) {
                priceDisplay = `AED ${parseFloat(book.price).toFixed(2)}`;
            }

            // Display the book information, title, image, and stock
            li.appendChild(img);
            li.appendChild(document.createTextNode(title));
            li.appendChild(document.createElement('br'));
            li.appendChild(document.createTextNode(`Grade: ${book.grade}`));
            li.appendChild(document.createElement('br'));
            li.appendChild(document.createTextNode(`${priceDisplay}`));

            // Fix the duplicate count display logic
            const countKey = `${book.id.split('-')[0]}-${book.grade}`;  
            const countDisplay = `No. in stock: ${duplicateCounts[countKey] || 1}`;
            li.appendChild(document.createElement('br'));
            li.appendChild(document.createTextNode(countDisplay));

            const isbnInfo = document.createElement('span');
            isbnInfo.textContent = `ISBN: ${book.id}`;
            isbnInfo.style.display = 'none'; // Hide it initially
            li.appendChild(isbnInfo); // Append the ISBN info to the list item


            li.onclick = (event) => {
                event.stopPropagation(); // Prevent click event from bubbling up to the document


                listItems.forEach(item => {
                    item.style.filter = 'none'; // Remove blur
                    const isbnSpan = item.querySelector('span');
                    if (isbnSpan) {
                        isbnSpan.style.display = 'none'; // Hide ISBN
                    }
                });


                if (isbnInfo.style.display === 'none') {
                    isbnInfo.style.display = 'inline'; // Show ISBN
                    li.style.height = 'auto'; // Expand the clicked item
                    li.style.filter = 'none'; // Remove blur from the clicked item
                } else {
                    isbnInfo.style.display = 'none'; // Hide ISBN
                }


                li.style.filter = 'none'; // Ensure the clicked item is not blurred
                listItems.forEach(item => {
                    if (item !== li) {
                        item.style.filter = 'blur(5px)'; // Blur the other items
                    }
                });
            };

            listItems.push(li); // Add the current item to the listItems array

            bookList.appendChild(li);
        })
        .catch(error => {
            console.error('Error fetching book image from Google:', error);


            const img = document.createElement('img');
            img.src = `https://images-na.ssl-images-amazon.com/images/P/${isbn}.L.jpg`; // Fallback Amazon image
            img.alt = 'No cover image available';
            img.style.maxWidth = '200px';  // Adjust the image size as needed


            let priceDisplay = 'Price not available';
            if (book.price && !isNaN(book.price)) {
                priceDisplay = `AED ${parseFloat(book.price).toFixed(2)}`;
            }

            li.appendChild(img);
            li.appendChild(document.createTextNode(` Title: ${book.name}, ISBN: ${book.id}, Grade: ${book.grade}, ${priceDisplay}`));
            bookList.appendChild(li);
        });
});


            document.addEventListener('click', () => {
                listItems.forEach(item => {
                    item.style.height = '260px'; // Reset height to auto
                    item.style.filter = 'none'; // Remove blur from all items
                    const isbnSpan = item.querySelector('span');
                    if (isbnSpan) {
                        isbnSpan.style.display = 'none'; // Hide ISBN
                    }
                });
            });
        }


        function searchBooks() {
            const searchTerm = document.getElementById('search-input').value.toLowerCase();
            const filteredBooks = uniqueBooks.filter(book => book.name.toLowerCase().includes(searchTerm));
            displayBooks(filteredBooks);
        }


        document.getElementById('search-button').addEventListener('click', searchBooks);


        fetchBooks();
    </script>
</body>`your text`
</html>

I tried doing all sorts of things to mitigate this issue, but this will not budge. If you need anything else, I’ll provide it as promptly as I can.

The errors I get