Build a Bookmark Manager with Node.js

Build a full-stack bookmark manager with a Node.js and Express backend.

Introduction

30 Second Summary

Every time you use a web app, the same invisible pattern plays out. Your browser sends a request, a server somewhere processes it, and a response comes back. This pattern powers everything from social media feeds to online shopping carts.

In this project, you will build a Bookmark Manager from scratch using Node.js and Express. You will create a web page that talks to a server you wrote, saving and deleting bookmarks without ever refreshing the browser.

What You'll Build

You type a website title and URL into a form, click Add, and the bookmark appears on the page instantly. Click Delete on any bookmark and it vanishes. No page refresh, no magic. Just your frontend talking to your backend.

By the end of this project, you'll have:

  • A working full-stack web app where you add a bookmark title and URL, click a button, and it saves to your server and shows up on the page instantly.
  • A backend API you built from scratch that handles saving, retrieving, and deleting bookmarks through HTTP endpoints.
  • A live browser-to-server connection using JavaScript's fetch() function that updates the page dynamically without any page refresh.
  • Secret Mission: A real-time search bar that filters your bookmarks as you type.

Are there any prerequisites?

None. This guide walks you through installing everything you need, including Node.js and VS Code. No prior experience with servers or backend development is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Project

Before you write any code, you need a project folder with the right tools installed. This step gets your development environment ready so everything runs smoothly when you start building.

You'll create a project folder on your Desktop, install Express (the web framework that powers your server), and set up the empty files your app will use.

In this step, get ready to:
  • Confirm Node.js is installed and the right version is running.
  • Create your project folder and install Express.
  • Set up the file structure for your app.
Open your terminal and check Node.js

Your project uses Node.js to run JavaScript outside the browser. Before you can install anything, you need to confirm Node.js is ready.

  • In VS Code, press Ctrl+` (backtick key, usually above Tab) to open the integrated terminal.
  • Check your Node.js version by running this command:
node --version

Look at the output in your terminal and find your situation below.

✔️ I see version 24 or higher

You're all set. Your Node.js version is compatible with this project. Continue to the next substep below.

ⓧ I see an older version

Your Node.js version is too old for Express 5.x, which requires Node.js 18 or higher. You need to upgrade to version 24 or later.

  • Go to https://nodejs.org/en/download in your browser.
  • Download and run the LTS installer for your operating system.
  • Follow the installer prompts, accepting the default settings.
  • Close and reopen VS Code so the terminal picks up the new version.
  • Run node --version again to confirm it now shows v24 or higher.

ⓧ Command not found

Node.js is not installed on your machine. You need to install it before continuing.

  • Go to https://nodejs.org/en/download in your browser.
  • Download the LTS installer for your operating system.
  • Run the installer and accept all default settings.
  • Close and reopen VS Code so the terminal picks up the new installation.
  • Run node --version again to confirm it now shows v24 or higher.
Create your project folder and install Express

Every project needs its own folder to keep files organized. You'll create one on your Desktop so it's easy to find.

  • Move to your Desktop by running this command:
cd ~/Desktop

Seeing an error on Windows?

If your Desktop folder has been moved to OneDrive, try cd ~/OneDrive/Desktop instead.

Help me find my Desktop path

  • Create your project folder and move into it by running these commands:
mkdir bookmark-manager
cd bookmark-manager
  • Initialize your project with npm by running this command:
npm init -y

You should see output showing a new package.json file was created with default values.

What does the -y flag do?

The -y flag tells npm to accept all default settings without asking you questions. It creates a package.json file that tracks your project's name, version, and dependencies.

  • Install Express by running this command:
npm install express

After a few seconds, you should see output confirming one package was added.

What just happened?

This command downloaded Express and saved it inside a node_modules folder in your project. It also added Express as a dependency in your package.json file so anyone who opens your project later can install the same packages.

Seeing permission errors or network failures?

Make sure you are inside the bookmark-manager folder (check with pwd on macOS or Get-Location on Windows). If you see network errors, check your internet connection and try the command again.

Help me troubleshoot npm install

Create your file structure

Your app needs four files organized into a specific structure. The server.js file lives at the project root, and three frontend files live inside a public/ folder.

  • Create the public folder and all project files by running the command for your operating system:

macOS

mkdir public
touch server.js
touch public/index.html public/style.css public/app.js

Windows

mkdir public
New-Item server.js -ItemType File
New-Item public/index.html -ItemType File
New-Item public/style.css -ItemType File
New-Item public/app.js -ItemType File
  • Confirm all files were created by running:
ls

Before you look at the output, what do you expect to see listed?

You should see node_modules, package-lock.json, package.json, public, and server.js in the output.

  • Check the contents of the public folder by running:
ls public

You should see app.js, index.html, and style.css listed.

Missing a file?

If any file is missing from the ls output, re-run the file creation command for that specific file. Make sure you are inside the bookmark-manager folder when you run it.

Help me figure out why my files are missing

Why this file structure?

The server.js file is your backend. It runs on Node.js and handles requests.

The public/ folder holds everything the browser sees: your HTML page, styling, and frontend JavaScript. Express will serve these files directly to anyone who visits your app.

Your project skeleton is ready. Next up, you'll write actual code in these files to build a working web page served by Express.

Build and Serve Your Frontend

Your project folder is set up with Express installed and empty files ready to fill in. Now it's time to see something real in the browser.

Before wiring up any data or logic, you need a visible page you can look at and interact with. This step gets your Express server running and serving a styled HTML page so you can watch your bookmark manager take shape.

In this step, get ready to:
  • Write a minimal Express server that serves static files from the public/ folder.
  • Build an HTML page with a heading, a form, and an empty list area.
  • Add CSS to make it look clean and modern.
Write the Express server

Your server.js file is currently empty. You need a few lines of code to tell Express to serve any file inside your public/ folder when someone visits your site.

  • Open server.js in VS Code and paste this code:
// Load the Express library
const express = require('express');
const path = require('path');

// Create the app and set the port
const app = express();
const PORT = 3000;

// Serve all files inside the public/ folder
app.use(express.static(path.join(__dirname, 'public')));

// Start the server
app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

What does this code do?

  • require('express') loads the Express library you installed in the previous step.
  • express.static(path.join(__dirname, 'public')) tells Express to serve any file inside public/ when a browser requests it.
  • app.listen(PORT) starts the server on port 3000, making your site available at http://localhost:3000.
  • Save server.js.
  • Open the VS Code terminal by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Start the server by running this command:
node server.js
  • You should see Server running at http://localhost:3000 printed in the terminal.

Why is the terminal stuck?

The server is actively running and waiting for requests. Your terminal will not accept new commands until you stop the server with Ctrl+C. Leave it running for now.

Don't see the message?

  • Check that you saved server.js before running the command.
  • Make sure your terminal is in the project folder (not a parent or subfolder).
  • If you see Cannot find module 'express', run npm install express again and retry.

Help me debug why my Express server won't start.

Build the HTML page

With the server running, anything you put in the public/ folder is now accessible in your browser. Time to build the page structure.

  • Open public/index.html in VS Code and paste this code:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bookmark Manager</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>My Bookmarks</h1>

    <form id="bookmark-form">
      <input type="text" id="title-input" placeholder="Bookmark title" required>
      <input type="url" id="url-input" placeholder="https://example.com" required>
      <button type="submit">Add Bookmark</button>
    </form>

    <div id="bookmark-list"></div>
  </div>

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

What does this HTML do?

  • The <form> contains two inputs (title and URL) and a submit button. This is where users will type their bookmark details.
  • The empty <div id="bookmark-list"> is where bookmarks will appear later once JavaScript fills it in.
  • The <script src="app.js"> tag links to the JavaScript file that will handle user interactions in a later step.
  • Save public/index.html.
  • Open your browser and visit http://localhost:3000.
  • You should see the heading "My Bookmarks", two input fields, and an "Add Bookmark" button. It looks plain and unstyled for now.

See a blank page?

  • Confirm you saved index.html inside the public/ folder, not in the project root.
  • Check the terminal to confirm the server is still running. If you accidentally closed it, run node server.js again.

Help me figure out why my HTML page isn't showing at localhost:3000.

Style the page with CSS

The page works but looks bare. You will add CSS to give it a modern, card-based layout with clean spacing and colour.

  • Open public/style.css in VS Code and paste the base layout styles:
/* Reset default browser spacing */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Page background and padding */
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background-color: #f0f2f5;
  padding: 40px 20px;
}

/* Centre content in a narrow column */
.container {
  max-width: 600px;
  margin: 0 auto;
}

/* Main heading */
h1 {
  color: #1a1a2e;
  margin-bottom: 24px;
  font-size: 2rem;
}

What do these styles do?

The * reset removes default browser margins so you start from a clean slate. The .container centres everything in a 600px column, keeping the page readable.

  • Save style.css and refresh your browser.
  • You should see the page centred on a light grey background with the heading and form pulled in from the edges.
  • Back in style.css, add the form and input styles below your existing code:
/* Form card layout */
#bookmark-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 32px;
  background: white;
  padding: 24px;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* Input field styling */
input {
  padding: 12px 16px;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  font-size: 1rem;
  transition: border-color 0.2s;
}

/* Blue highlight when an input is focused */
input:focus {
  outline: none;
  border-color: #4361ee;
}

What do these styles do?

The form becomes a white card with rounded corners and a subtle shadow. Inputs get generous padding and a blue border highlight when you click into them.

  • Save style.css and refresh your browser.
  • The form should now appear as a white card with rounded inputs. Click into one of the input fields to see the blue border highlight.
  • Add the button and utility styles below your existing code in style.css:
/* Primary button */
button {
  padding: 12px 24px;
  background-color: #4361ee;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: #3a56d4;
}

/* Delete button variant */
.delete-btn {
  padding: 8px 12px;
  background-color: #ef4444;
  font-size: 0.85rem;
}

.delete-btn:hover {
  background-color: #dc2626;
}

/* Placeholder when no bookmarks exist */
.empty-message {
  text-align: center;
  color: #888;
  padding: 40px;
  font-size: 1.1rem;
}

What do these styles do?

The main button gets a solid blue background that darkens on hover. The .delete-btn and .empty-message classes prepare styles for elements that will appear once you start adding bookmarks.

  • Save style.css and refresh your browser.
  • The Add Bookmark button should now be a bold blue rectangle with rounded corners.
  • Add the final bookmark card styles at the bottom of style.css:
/* Individual bookmark card */
.bookmark-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: white;
  padding: 16px 20px;
  border-radius: 10px;
  margin-bottom: 12px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

/* Bookmark title link */
.bookmark-info a {
  color: #4361ee;
  text-decoration: none;
  font-weight: 500;
  font-size: 1.1rem;
}

.bookmark-info a:hover {
  text-decoration: underline;
}

/* URL text below the title */
.bookmark-info p {
  color: #666;
  font-size: 0.85rem;
  margin-top: 4px;
}

What do these styles do?

Each bookmark will appear as its own white card with the title as a clickable blue link and the URL in smaller grey text below. These styles activate once you wire up the backend and start saving bookmarks.

  • Save style.css.

Before you refresh, what do you expect the page to look like now that all styles are in place?

  • Refresh your browser at http://localhost:3000.

You should see a clean, modern Bookmark Manager page with a blue Add Bookmark button, rounded input fields inside a white card, all centred on a light grey background.

Styles not showing up?

  • Make sure style.css is saved inside the public/ folder (not the project root).
  • Try a hard refresh: Ctrl+Shift+R (Windows) or Cmd+Shift+R (macOS) to bypass browser cache.
  • Check that your index.html has <link rel="stylesheet" href="style.css"> in the <head> section.

Help me figure out why my CSS styles are not appearing in the browser.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files: server.js, public/index.html, and public/style.css.

ⓧ I'd like to double check the full code

Your server.js should look like this:

const express = require('express');
const path = require('path');

const app = express();
const PORT = 3000;

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

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

Your public/index.html should look like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bookmark Manager</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>My Bookmarks</h1>

    <form id="bookmark-form">
      <input type="text" id="title-input" placeholder="Bookmark title" required>
      <input type="url" id="url-input" placeholder="https://example.com" required>
      <button type="submit">Add Bookmark</button>
    </form>

    <div id="bookmark-list"></div>
  </div>

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

Your public/style.css should contain these sections in order. Scroll up and confirm each one is present:

  • Base layout styles (*, body, .container, h1).
  • Form and input styles (#bookmark-form, input, input:focus).
  • Button styles (button, button:hover, .delete-btn, .delete-btn:hover, .empty-message).
  • Bookmark card styles (.bookmark-item, .bookmark-info a, .bookmark-info a:hover, .bookmark-info p).

Your frontend is alive and looking sharp. Next up, you will try clicking that Add Bookmark button and discover it does not actually save anything yet. That is the gap your backend API will fix.

Create the Backend API

Your Express server is up and running, serving a styled bookmark form to the browser.

But here is the problem: that form cannot actually do anything yet. Let's prove it.

  • Switch back to your browser showing the Bookmark Manager at http://localhost:3000.
  • Type Google in the title field and https://google.com in the URL field.
  • Click Add Bookmark.

The page refreshes and your form goes blank. Nothing was saved. The frontend has no backend to talk to. You need API routes on the server that can receive, store, and return bookmark data.

In this step, get ready to:
  • Add JSON parsing middleware and in-memory storage to the server.
  • Create API routes for saving, retrieving, and deleting bookmarks.
  • Verify the API returns data by visiting the route in your browser.
Set up middleware, storage, and the GET route

For the server to understand bookmark data sent from the browser, it needs middleware that parses JSON request bodies. It also needs somewhere to store bookmarks and a route that returns them.

  • In VS Code, open server.js.
  • Find the line that says app.use(express.static(path.join(__dirname, 'public')));.
  • Replace everything between const PORT = 3000; and app.listen(...) with the following code:
// Parse JSON data sent in request bodies
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

// In-memory storage for bookmarks
let bookmarks = [];
let nextId = 1;

// Return all saved bookmarks
app.get('/api/bookmarks', (req, res) => {
  res.json(bookmarks);
});

What does this code do?

  • app.use(express.json()) tells Express to automatically parse JSON data in incoming requests. Without this, req.body would be undefined.
  • bookmarks is a simple array that holds all saved bookmarks in server memory.
  • nextId gives each bookmark a unique number so you can reference and delete specific ones.
  • The GET route at /api/bookmarks responds with the full bookmarks array as JSON.
  • Save server.js.
  • In your VS Code terminal, stop the running server by pressing Ctrl+C.
  • Restart it by running:
node server.js

Before you check the result, what do you expect to see if you visit /api/bookmarks in your browser?

  • Open your browser and visit http://localhost:3000/api/bookmarks.

You should see an empty JSON array: []. Your GET route is working and returning the (currently empty) bookmarks list.

Seeing an error instead of []?

  • If your terminal shows a syntax error, check that the app.get('/api/bookmarks', ...) block has matching opening and closing braces and parentheses.
  • Make sure app.use(express.json()) appears ABOVE the app.use(express.static(...)) line.
  • If you see Cannot GET /api/bookmarks, your route might be placed after app.listen(). Move it above that block.

Why isn't my /api/bookmarks route returning []?

Add the POST route

The GET route can return bookmarks, but you have no way to create them yet. A POST route accepts data from the browser and saves a new bookmark to the array.

  • In server.js, add the following code below the GET route and above app.listen(...):
// Create a new bookmark
app.post('/api/bookmarks', (req, res) => {
  const { title, url } = req.body;

  if (!title || !url) {
    return res.status(400).json({ error: 'Title and URL are required' });
  }

  const bookmark = {
    id: nextId++,
    title,
    url,
    createdAt: new Date().toISOString()
  };

  bookmarks.push(bookmark);
  res.status(201).json(bookmark);
});

What does this code do?

  • const { title, url } = req.body pulls the title and URL out of the JSON data sent by the browser.
  • The if (!title || !url) check rejects requests that are missing required data, returning a 400 error.
  • A bookmark object is created with a unique id, the provided title and URL, and a timestamp.
  • res.status(201).json(bookmark) sends the new bookmark back to the browser with a 201 Created status code.
  • Save server.js.
  • Stop the server with Ctrl+C and restart it by running:
node server.js

You can test the POST route directly from the browser's developer console. This is exactly what your frontend JavaScript will do in the next step.

  • Open your browser to http://localhost:3000.
  • Open DevTools by pressing Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
  • Paste the following command into the Console tab and press Enter:
fetch('/api/bookmarks', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Google', url: 'https://google.com' })
}).then(r => r.json()).then(console.log)

You should see a bookmark object logged in the console with an id, title, url, and createdAt timestamp.

  • Now visit http://localhost:3000/api/bookmarks in a new tab.

You should see your bookmark inside the array. The POST route saved it and the GET route returned it.

Console shows an error or undefined?

  • If you see a network error, make sure the server is still running in your terminal.
  • If /api/bookmarks still shows [] after posting, check that app.use(express.json()) is in your file. Without it, req.body is undefined and the validation check rejects the request.

Help me debug my POST /api/bookmarks route

Add the DELETE route

Users will need a way to remove bookmarks they no longer want. A DELETE route finds a bookmark by its ID and removes it from the array.

  • In server.js, add the following code below the POST route and above app.listen(...):
// Delete a bookmark by ID
app.delete('/api/bookmarks/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const index = bookmarks.findIndex(b => b.id === id);

  if (index === -1) {
    return res.status(404).json({ error: 'Bookmark not found' });
  }

  bookmarks.splice(index, 1);
  res.status(204).send();
});

What does this code do?

  • :id in the route path is a URL parameter. Express captures it in req.params.id.
  • findIndex() searches the array for the bookmark with the matching ID. If none is found, it returns -1 and the server responds with a 404 error.
  • splice(index, 1) removes the bookmark from the array. The server responds with 204 No Content to confirm deletion.
  • Save server.js.
  • Stop the server with Ctrl+C and restart it by running:
node server.js

Before you check the result, what do you expect to see at /api/bookmarks now that you restarted the server? Remember where bookmarks are stored.

  • Visit http://localhost:3000/api/bookmarks in your browser.

You see an empty array []. The bookmark you added earlier is gone because restarting the server clears in-memory storage. This is expected for a learning project.

Why did my bookmark disappear?

Your bookmarks live in a JavaScript variable inside the server process. When you stop the server, that variable is destroyed. When you start it again, the array starts fresh and empty.

A production app would use a database to keep bookmarks permanently. For learning how frontends and backends communicate, in-memory storage keeps things simple.

Server won't start?

  • Check your terminal for a line number in the error message. That points to the exact spot with a typo or missing bracket.
  • Make sure the DELETE route is placed between the POST route and app.listen().
  • Confirm that every opening { and ( has a matching closing } and ).

Help me fix a syntax error in my server.js

✔️ Awesome, I've got everything!

Great. Double check you have saved server.js and your server is running without errors.

ⓧ I'd like to double check the full code

Scroll up and confirm your server.js contains each of these sections in order:

  • The require statements and const app = express() setup (from Step 2).
  • app.use(express.json()) followed by app.use(express.static(...)).
  • let bookmarks = [] and let nextId = 1.
  • The GET route at /api/bookmarks.
  • The POST route at /api/bookmarks.
  • The DELETE route at /api/bookmarks/:id.
  • app.listen(PORT, ...) at the very bottom.

Your backend API is complete. It can store bookmarks, return them, and delete them. Next up, you will connect the frontend page to these routes so the form actually works.

Connect Frontend to Backend

In the last step, you proved your API works by visiting /api/bookmarks directly in the browser and seeing the empty [] response. The server is ready to receive and return bookmarks.

But the page itself still cannot use that API. Try clicking Add Bookmark right now and the page just refreshes. You need frontend JavaScript that sends fetch requests to those API endpoints and updates the page dynamically.

In this step, get ready to:
  • Write a function that fetches bookmarks from the server and renders them on the page.
  • Add a form submit handler that sends bookmark data to the API without refreshing.
  • Add delete functionality so bookmarks can be removed with one click.
Load bookmarks from the server

The Fetch API is built into every modern browser. It lets your JavaScript code send HTTP requests to a server and receive data back, all without refreshing the page.

  • Open public/app.js in VS Code (it should be empty) and add this code:
// Get references to the HTML elements we'll interact with
const form = document.getElementById('bookmark-form');
const titleInput = document.getElementById('title-input');
const urlInput = document.getElementById('url-input');
const bookmarkList = document.getElementById('bookmark-list');

// Fetch all bookmarks from the server and display them
async function loadBookmarks() {
  const response = await fetch('/api/bookmarks');
  const bookmarks = await response.json();
  displayBookmarks(bookmarks);
}

What does this code do?

The first four lines grab references to the HTML elements you created earlier. These variables let you read input values and update the page without searching the DOM repeatedly.

The loadBookmarks() function sends a GET request to /api/bookmarks, converts the response to a JavaScript array, and passes it to a display function you'll write next.

  • Below the loadBookmarks() function, add the display function that turns bookmark data into visible HTML:
// Render bookmarks as HTML, or show a message if the list is empty
function displayBookmarks(bookmarks) {
  if (bookmarks.length === 0) {
    bookmarkList.innerHTML = '<p class="empty-message">No bookmarks yet. Add your first one above!</p>';
    return;
  }

  bookmarkList.innerHTML = bookmarks.map(bookmark => `
    <div class="bookmark-item">
      <div class="bookmark-info">
        <a href="${bookmark.url}" target="_blank">${bookmark.title}</a>
        <p>${bookmark.url}</p>
      </div>
      <button class="delete-btn" onclick="deleteBookmark(${bookmark.id})">Delete</button>
    </div>
  `).join('');
}

What does this code do?

If the bookmarks array is empty, it shows a friendly "No bookmarks yet" message. Otherwise, it loops through each bookmark and creates an HTML card with the title as a clickable link, the URL displayed below it, and a Delete button.

The .map() method transforms each bookmark object into an HTML string. The .join('') at the end combines all those strings into one block of HTML that gets inserted into the page.

  • At the very bottom of the file, add this line to load bookmarks when the page first opens:
loadBookmarks();
  • Save public/app.js.
  • In the VS Code terminal, stop the server with Ctrl+C and restart it by running:
node server.js
  • Refresh http://localhost:3000 in your browser. You should see the message "No bookmarks yet. Add your first one above!" in the center of the page.

Don't see the message?

  • Make sure you saved public/app.js. Unsaved changes won't appear in the browser.
  • Open the browser console (F12 then the Console tab) and check for red error messages. A typo in a variable name or missing bracket will show up here.
  • Check that your HTML has <script src="app.js"></script> at the bottom of the body. Without this, the browser never loads your JavaScript.

Why isn't my loadBookmarks function showing the empty message?

Handle the form submission

Right now, clicking Add Bookmark triggers the browser's default form behavior, which refreshes the page. You need to intercept that event and send the data to your API instead.

  • In public/app.js, add this code above the loadBookmarks(); call at the bottom of the file:
// Listen for form submissions to add new bookmarks
form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const title = titleInput.value.trim();
  const url = urlInput.value.trim();

  if (!title || !url) return;

  const response = await fetch('/api/bookmarks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, url })
  });

  if (response.ok) {
    titleInput.value = '';
    urlInput.value = '';
    loadBookmarks();
  }
});

What does this code do?

  • event.preventDefault() stops the browser from refreshing the page when the form is submitted.
  • It reads the title and URL values from the input fields and sends them to the server as a POST request with a JSON body.
  • If the server responds with success, it clears the form and calls loadBookmarks() to refresh the list with the newly added bookmark.
  • Save public/app.js.
  • Stop the server with Ctrl+C and restart it by running node server.js.
  • Refresh your browser and type NextWork in the title field and https://nextwork.org in the URL field.
  • Click Add Bookmark. The bookmark should appear instantly in the list below the form, and the page should not refresh.

Page refreshes instead of adding the bookmark?

  • Make sure event.preventDefault() is the first line inside your submit handler. Without it, the browser's default refresh behavior takes over.
  • Check that the form variable references bookmark-form (with a hyphen). A mismatched ID means the event listener attaches to nothing.

My form submission isn't working. Help me figure out why.

Add delete functionality

Each bookmark card already has a Delete button in the HTML that your displayBookmarks() function renders. That button calls deleteBookmark(id) on click. You just need to write that function.

  • In public/app.js, add this code above the loadBookmarks(); call at the bottom of the file (below the form event listener):
// Delete a bookmark by sending a DELETE request to the server
async function deleteBookmark(id) {
  const response = await fetch(`/api/bookmarks/${id}`, {
    method: 'DELETE'
  });

  if (response.ok) {
    loadBookmarks();
  }
}

What does this code do?

It sends a DELETE request to /api/bookmarks/:id with the specific bookmark's ID in the URL. If the server confirms deletion, it calls loadBookmarks() to re-fetch the updated list and refresh what's displayed on the page.

  • Save public/app.js.
  • Stop the server with Ctrl+C and restart it by running node server.js.

Before you test, think about what should happen when you add a bookmark and then click its Delete button. What do you expect to see?

  • Refresh your browser.
  • Add a bookmark (try GitHub with URL https://github.com).
  • The bookmark appears in the list instantly with no page refresh.
  • Click the red Delete button next to the bookmark. It disappears and the "No bookmarks yet" message returns.

That's it. Your frontend is talking to your backend. The full-stack loop is complete.

Delete button not working?

  • Open the browser console (F12) and look for a red error saying "deleteBookmark is not defined". This means the function name in your onclick attribute doesn't match your function declaration.
  • Make sure you restarted the server after saving. Changes to app.js are served by Express, so a hard refresh (Ctrl+Shift+R) in the browser can also help clear cached files.

My delete button isn't removing bookmarks. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you've saved public/app.js and your bookmarks are adding and deleting successfully.

ⓧ I'd like to double check the full code

Here is the complete public/app.js file. Compare it with yours and make sure everything matches.

const form = document.getElementById('bookmark-form');
const titleInput = document.getElementById('title-input');
const urlInput = document.getElementById('url-input');
const bookmarkList = document.getElementById('bookmark-list');

async function loadBookmarks() {
  const response = await fetch('/api/bookmarks');
  const bookmarks = await response.json();
  displayBookmarks(bookmarks);
}
function displayBookmarks(bookmarks) {
  if (bookmarks.length === 0) {
    bookmarkList.innerHTML = '<p class="empty-message">No bookmarks yet. Add your first one above!</p>';
    return;
  }

  bookmarkList.innerHTML = bookmarks.map(bookmark => `
    <div class="bookmark-item">
      <div class="bookmark-info">
        <a href="${bookmark.url}" target="_blank">${bookmark.title}</a>
        <p>${bookmark.url}</p>
      </div>
      <button class="delete-btn" onclick="deleteBookmark(${bookmark.id})">Delete</button>
    </div>
  `).join('');
}
form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const title = titleInput.value.trim();
  const url = urlInput.value.trim();

  if (!title || !url) return;

  const response = await fetch('/api/bookmarks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, url })
  });

  if (response.ok) {
    titleInput.value = '';
    urlInput.value = '';
    loadBookmarks();
  }
});

async function deleteBookmark(id) {
  const response = await fetch(`/api/bookmarks/${id}`, {
    method: 'DELETE'
  });

  if (response.ok) {
    loadBookmarks();
  }
}

loadBookmarks();

You just built a working full-stack application. The browser sends requests, the server processes them, and the page updates dynamically. This is the exact pattern behind every web app you use daily. Next up, you'll tackle a Secret Mission to make your app even more useful.

Secret mission

Add Real-Time Search

Your bookmark list works, but as it grows, finding the right link means scrolling through everything. In this mission, you'll add a search bar that filters your bookmarks in real time as you type.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally on your machine, so there are no ongoing costs. Decide whether to keep your resources, pause them to come back later, or delete them entirely.

Resources you used:

  • Your project folder on the Desktop (contains server.js, the public/ folder, and node_modules/).

What about my bookmarks?

Bookmarks are stored in memory, so they reset every time you stop the server. This is expected for a learning project. No data persists on disk after the server stops.

Keep everything running

No action needed. Choose this if you're still actively building or want to run the app again later.

  • Run node server.js from inside your project folder any time you want to use the Bookmark Manager.
  • Open http://localhost:3000 in your browser to access it.
  • Press Ctrl+C in the terminal to stop the server when you're done.

Pause - I'll come back to this later

Shut down the running server to free up your terminal. Your project files stay on the Desktop for next time.

  • In the VS Code terminal, press Ctrl+C to stop the server (if it's still running).
  • Close VS Code. Your project folder remains on the Desktop, ready to reopen.
  • When you return, open the project folder in VS Code and run node server.js to start again.

Delete - I don't want to use this again

Remove all project files from your machine. This deletes your code permanently.

  • In the VS Code terminal, press Ctrl+C to stop the server (if it's still running).
  • Close VS Code.

Delete your project folder from the Desktop. Open a terminal and run the command for your operating system.

On macOS or Linux:

cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your-project-folder-name"]]

On Windows (PowerShell):

cd ~\Desktop
Remove-Item -Recurse -Force [[PROJECT_FOLDER="your-project-folder-name"]]

Confirm the folder is gone by checking your Desktop.

Nice Work!

Nice Work!

You just built a full-stack web application from scratch. Your Bookmark Manager proves you understand the core pattern behind every web app: a browser sends requests, a server processes them, and the server sends responses back.

You've learned how to:

  • Build a full-stack web application with a frontend that talks to a backend through HTTP requests.
  • Create an Express server with REST API routes that handle GET, POST, and DELETE requests.
  • Connect a browser interface to a server API using the fetch() function to send requests and update the page without refreshing.
  • Secret Mission: Added real-time search filtering with event-driven programming.

Ready to quiz yourself?