Build a Bookmark Saver (Full Stack)

Build a full stack app with an Express API and vanilla JS frontend.

Introduction

30 Second Summary

Every website you use works the same way behind the scenes. Your browser asks a server for information, and the server sends it back.

In this project, you will build a Bookmark Saver app with two distinct layers: a frontend that runs in your browser and a backend server that stores your data. You'll see exactly how the two halves talk to each other.

What You'll Build

A full stack web application with a vanilla HTML/CSS/JavaScript frontend and a Node.js/Express backend API that saves bookmarks to a JSON file.

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

  • An Express server with two REST API routes (GET and POST) that read and write bookmark data.
  • A frontend page with a form and a dynamically rendered bookmark list powered by the Fetch API.
  • A JSON file that persists your bookmarks between server restarts.
  • Secret Mission: Add a delete button to each bookmark that sends a DELETE request to remove it from the server.

Are there any prerequisites?

You should be comfortable with basic HTML, CSS, and JavaScript. No backend or server experience is needed.

Set Up the Project

Before you can build anything, you need a place for your code to live and a framework to handle the server logic. Every full stack project starts with this foundation step.

In this step, you'll create your project folder, install Express (the backend framework), set up your file structure, and confirm everything works with a "Hello World" server.

In this step, get ready to:
  • Create your project folder and install Express.
  • Set up the file structure for your frontend, backend, and data.
  • Start the server and verify it responds at localhost:3000.
Create the project folder and install Express

You need a dedicated folder on your Desktop for this project, plus a package.json file that tracks your project's dependencies.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir bookmark-saver
cd bookmark-saver

Your terminal should now show you're inside the bookmark-saver folder on your Desktop.

  • Initialize a new Node.js project by running:
npm init -y

What does this command do?

The npm init -y command creates a package.json file with default settings. This file tracks your project's name, version, and any packages (dependencies) you install.

  • Confirm it worked by running:
ls

You should see package.json in the output.

  • Install Express by running:
npm install express

What does this command do?

This downloads the Express framework from npm (Node's package manager) and saves it as a dependency in your package.json. Express handles all the HTTP request/response logic for your server.

  • Confirm the installation by running:
ls

You should now see node_modules, package.json, and package-lock.json in the output.

Seeing an error during install?

If you see a "permission denied" error, try running the command with sudo on macOS/Linux, or open your terminal as Administrator on Windows.

If you see "npm: command not found", Node.js may not be installed correctly. Confirm by running node --version in your terminal.

Help me fix my npm install error

Create the file structure

Your project needs three areas: a server file at the root, a public/ folder for frontend files, and a data/ folder for storing bookmarks.

  • Create both folders by running:
mkdir data public
  • Confirm the folders exist by running:
ls

You should see data and public alongside your existing files.

  • Open your project in your code editor. In the top menu bar, click **File**.
  • Click **Open Folder**.
  • Select the bookmark-saver folder on your Desktop, then click Open.

You should see your folder structure in the sidebar: data/, public/, node_modules/, and package.json.

  • Create a new file inside the data/ folder called bookmarks.json. Right-click the data folder in the sidebar and select New File.
  • Type bookmarks.json as the file name and press Enter.
  • Add the following content to the file:
[]

Why an empty array?

This JSON file is your database. The empty array [] means "no bookmarks saved yet." Your server will read from and write to this file every time someone adds a bookmark.

  • Save bookmarks.json.
  • Create another new file at the project root (not inside any folder) called server.js. Right-click the empty space below your folders in the sidebar and select New File.
  • Type server.js as the file name and press Enter.

Your file structure is now complete. You have server.js at the root, data/bookmarks.json ready for storage, and an empty public/ folder waiting for your frontend files.

Write and run the Hello World server

Your server needs to do one thing right now: start up and respond to a browser request. This confirms Express is installed correctly and your project is wired up.

  • Open server.js in your editor and add the following code:
// Import Express framework
const express = require('express');

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

// When someone visits the homepage, respond with 'Hello World'
app.get('/', (req, res) => {
  res.send('Hello World');
});

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

What does this code do?

  • require('express') loads the Express package you just installed.
  • express() creates your app, which is the object you attach routes and settings to.
  • app.get('/', ...) creates a route that listens for GET requests at the homepage path. When someone visits, it sends back "Hello World".
  • app.listen(PORT) starts the server on port 3000 and prints the URL to your terminal.
  • Save server.js.
  • Start your server by running this command in your terminal:
node server.js

You should see Server running at http://localhost:3000 printed in your terminal.

  • Open your browser and go to http://localhost:3000.

You should see "Hello World" displayed on the page. Your server is alive and responding to requests.

Don't see Hello World in the browser?

  • Make sure your terminal still shows the "Server running" message. If you see an error instead, check server.js for typos (especially missing parentheses or backticks).
  • If you see "Error: Cannot find module 'express'", make sure you ran npm install express inside the bookmark-saver folder (not a parent folder).
  • If you see "EADDRINUSE", another process is already using port 3000. Close other terminal tabs or restart your terminal and try again.

Help me debug my Express server

✔️ Awesome, I've got everything!

Great. Make sure you've saved server.js and data/bookmarks.json.

ⓧ I'd like to double check the full code

Here's what server.js should look like:

const express = require('express');

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

app.get('/', (req, res) => {
  res.send('Hello World');
});

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

And data/bookmarks.json should contain:

[]

Your project foundation is solid. You have Express installed, your file structure ready, and a running server. Next up, you'll replace that "Hello World" response with real API routes that read and write bookmark data.

Build the API Routes

Your server is running and responding with "Hello World" at the root URL. Now it's time to build the actual functionality.

API routes are the endpoints your frontend will talk to. You'll create two: one to retrieve all saved bookmarks and one to save new ones to your JSON file.

In this step, get ready to:
  • Add a GET route that returns all saved bookmarks as JSON.
  • Add a POST route that saves a new bookmark to the JSON file.
  • Test both routes to confirm they work.
Add the GET /api/bookmarks route

Before adding the route, you need to import Node.js modules for reading files and constructing paths. You also need middleware that lets Express parse incoming JSON and serve static files.

  • In server.js, find these lines at the top of your file:
const express = require('express');

const app = express();
const PORT = 3000;
  • Replace them with this code, which adds file system and path modules plus middleware:
const express = require('express');
const fs = require('node:fs');
const path = require('node:path');

const app = express();
const PORT = 3000;
const DATA_FILE = path.join(__dirname, 'data', 'bookmarks.json');

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

What does this code do?

  • fs is Node's built-in file system module. It lets you read and write files on disk.
  • path helps construct file paths that work across operating systems.
  • DATA_FILE stores the full path to your bookmarks.json file so you don't repeat it in every route.
  • express.json() parses incoming JSON request bodies and populates req.body.
  • express.static() serves files from your public/ folder. Any file in that folder becomes accessible via the browser.

You won't see a change yet. The next chunk adds the route that uses these modules.

  • Now find the Hello World route in server.js:
app.get('/', (req, res) => {
  res.send('Hello World');
});
  • Replace it with this GET route:
// GET /api/bookmarks - return all bookmarks
app.get('/api/bookmarks', (req, res) => {
  const data = fs.readFileSync(DATA_FILE, 'utf8');
  const bookmarks = JSON.parse(data);
  res.json(bookmarks);
});

What does this route do?

  • fs.readFileSync(DATA_FILE, 'utf8') reads the entire contents of bookmarks.json as a text string.
  • JSON.parse(data) converts that string into a JavaScript array.
  • res.json(bookmarks) sends the array back to the client as a JSON response with the correct headers.
  • Save server.js.
  • Stop your server by pressing Ctrl+C in the terminal.
  • Restart the server by running:
node server.js
  • Open your browser and visit http://localhost:3000/api/bookmarks.
  • You should see an empty array: [].

Seeing an error or 'Cannot GET /api/bookmarks'?

  • Make sure you removed the old Hello World route. Having two app.get('/') handlers won't cause this error, but leftover typos might.
  • Check that data/bookmarks.json exists and contains []. If the file is missing, the server crashes on startup.
  • Confirm you saved the file and restarted the server after making changes.

Why is my GET route not returning data?

Add the POST /api/bookmarks route

The GET route reads bookmarks. Now you need a route that writes new bookmarks to the file.

  • In server.js, add this code directly below your GET route (above the app.listen line):
// POST /api/bookmarks - add a new bookmark
app.post('/api/bookmarks', (req, res) => {
  const { name, url } = req.body;

  const data = fs.readFileSync(DATA_FILE, 'utf8');
  const bookmarks = JSON.parse(data);

  bookmarks.push({ name, url });

  fs.writeFileSync(DATA_FILE, JSON.stringify(bookmarks, null, 2));
  res.status(201).json({ message: 'Bookmark added', bookmark: { name, url } });
});

What does this route do?

  • const { name, url } = req.body extracts the bookmark's name and URL from the incoming request body.
  • It reads the current bookmarks array from the file, pushes the new bookmark onto it, then writes the updated array back.
  • JSON.stringify(bookmarks, null, 2) formats the output with 2-space indentation so the file stays readable.
  • res.status(201) sends a 201 Created status code, which is the standard response for successful resource creation.
  • Save server.js.
  • Stop your server with Ctrl+C and restart it by running:
node server.js

You should see Server running at http://localhost:3000 in your terminal. The POST route won't show visible output in the browser yet. You'll test it next.

Server crashing on restart?

  • Check for missing closing brackets or parentheses. Every app.post( needs a matching ); at the end.
  • Make sure the POST route is placed above the app.listen() call, not below it.

Help me fix my server.js syntax error.

Test both routes

Your routes are in place. Time to confirm they actually work together by testing the full read-and-write cycle.

  • Open your browser and visit http://localhost:3000/api/bookmarks.
  • You should see [] (an empty array). This confirms the GET route works.

Now test the POST route by sending a request from the browser console.

  • Open your browser's Developer Tools by pressing Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
  • Paste the following fetch() call into the console and press Enter:
fetch('/api/bookmarks', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'NextWork', url: 'https://www.nextwork.org' })
}).then(res => res.json()).then(data => console.log(data));

What does this fetch call do?

This sends a POST request to your server with a JSON body containing a bookmark name and URL. The Fetch API is the browser's built-in way to make HTTP requests. Your frontend will use the same pattern in the next step.

  • You should see a response logged in the console: {message: 'Bookmark added', bookmark: {name: 'NextWork', url: 'https://www.nextwork.org'}}.
  • Now refresh the page at http://localhost:3000/api/bookmarks.
  • You should see your bookmark in the array instead of an empty [].

Why does the bookmark persist?

Your POST route writes to bookmarks.json on disk. The GET route reads from that same file. Because the data lives in a file (not just in memory), it survives server restarts. This is the simplest form of data persistence.

Still seeing an empty array after the POST?

  • Check the console response. If you see undefined or an error, the POST request may have failed.
  • Make sure app.use(express.json()) appears above your routes in server.js. Without it, req.body is undefined.
  • Open data/bookmarks.json in your editor. If the bookmark was saved, you'll see it there. If the file still shows [], the POST route is not writing correctly.

My POST request isn't saving bookmarks. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete server.js file at this point:

const express = require('express');
const fs = require('node:fs');
const path = require('node:path');

const app = express();
const PORT = 3000;
const DATA_FILE = path.join(__dirname, 'data', 'bookmarks.json');

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

// GET /api/bookmarks - return all bookmarks
app.get('/api/bookmarks', (req, res) => {
  const data = fs.readFileSync(DATA_FILE, 'utf8');
  const bookmarks = JSON.parse(data);
  res.json(bookmarks);
});

// POST /api/bookmarks - add a new bookmark
app.post('/api/bookmarks', (req, res) => {
  const { name, url } = req.body;

  const data = fs.readFileSync(DATA_FILE, 'utf8');
  const bookmarks = JSON.parse(data);

  bookmarks.push({ name, url });

  fs.writeFileSync(DATA_FILE, JSON.stringify(bookmarks, null, 2));
  res.status(201).json({ message: 'Bookmark added', bookmark: { name, url } });
});

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

Your backend API is complete. It can store and retrieve bookmarks reliably. Next up, you'll build a frontend page that gives users a real interface for interacting with these routes.

Build the Frontend

Your API is built and tested. You confirmed it can store and retrieve bookmarks. But typing fetch() commands into the browser console is not exactly a user-friendly experience.

Now you'll build the frontend that talks to your API. This means creating an HTML page with a form, styling it with CSS, and writing JavaScript that fetches bookmarks from your server and displays them as clickable links.

In this step, get ready to:
  • Create index.html with a form and bookmark list structure.
  • Style the page with CSS for a clean, centered layout.
  • Write a loadBookmarks() function that fetches and displays bookmarks from the API.
Create the HTML page

Every frontend starts with an HTML file that defines the page structure. Your page needs two things: a form where users type in bookmark details, and a list element where JavaScript will render the saved bookmarks.

  • Create a new file called index.html inside your public folder.
  • Paste the following code into public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bookmark Saver</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>Bookmark Saver</h1>
    <!-- Form for adding new bookmarks -->
    <form id="bookmark-form">
      <input type="text" id="name-input" placeholder="Bookmark name" required>
      <input type="url" id="url-input" placeholder="https://example.com" required>
      <button type="submit">Save Bookmark</button>
    </form>
    <!-- Bookmarks will be rendered here by JavaScript -->
    <ul id="bookmark-list"></ul>
  </div>
  <script src="app.js"></script>
</body>
</html>

What does this code do?

The form#bookmark-form has two inputs: one for the bookmark name and one for its URL. The required attribute prevents the form from submitting when either field is empty.

The ul#bookmark-list starts empty. JavaScript will fill it with bookmarks fetched from your API. The id attributes on each element give JavaScript a way to find and interact with them.

  • Save public/index.html.
  • Make sure your server is still running (if not, run node server.js in your terminal).
  • Open your browser to http://localhost:3000. You should see the heading "Bookmark Saver", two input fields, and a blue "Save Bookmark" button.

Seeing a blank page or "Cannot GET /"?

  • Make sure your file is named exactly index.html (not index.HTML or Index.html) and is inside the public folder.
  • Check that your server.js has the express.static middleware line from Step 2. Without it, Express does not serve files from public/.
  • If you changed server.js, you need to stop the server (Ctrl+C) and restart it with node server.js.

Why is my page not loading at localhost:3000?

Style the page with CSS

The page works, but raw HTML without styling is hard to look at. A bit of CSS will turn it into a clean, centered card layout.

  • Create a new file called style.css inside your public folder.
  • Paste the following styles into public/style.css:
/* Reset default browser styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

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

/* Centered card container */
.container {
  max-width: 600px;
  margin: 0 auto;
  background: white;
  padding: 32px;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

h1 {
  margin-bottom: 24px;
  color: #333;
}

/* Form layout */
form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 32px;
}

input {
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-size: 16px;
}

input:focus {
  outline: none;
  border-color: #4a90d9;
}

button {
  padding: 12px;
  background-color: #4a90d9;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;
}

button:hover {
  background-color: #357abd;
}

/* Bookmark list styles */
ul {
  list-style: none;
}

li {
  padding: 12px;
  border-bottom: 1px solid #eee;
}

li:last-child {
  border-bottom: none;
}

li a {
  color: #4a90d9;
  text-decoration: none;
  font-size: 16px;
}

li a:hover {
  text-decoration: underline;
}

What does this CSS do?

  • The * reset removes default margins and padding so every browser starts from the same baseline.
  • The .container centers the content and adds a white card with rounded corners and a subtle shadow.
  • The form uses flex-direction: column to stack the inputs vertically with consistent spacing.
  • The list items have bottom borders to separate each bookmark visually.
  • Save public/style.css.
  • Refresh your browser at http://localhost:3000. You should see a clean white card centered on a light gray background, with nicely styled inputs and a blue button.

Page still looks unstyled?

  • Check that style.css is in the public folder, not in the project root.
  • Verify the <link> tag in your HTML says href="style.css" (no folder prefix needed since both files are in public/).
  • Try a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass the browser cache.

Why isn't my CSS loading in the browser?

Add JavaScript to load bookmarks from the API

The page looks great, but it does not do anything yet. You need JavaScript that calls your GET /api/bookmarks endpoint and turns the response into visible bookmark links on the page.

This is the Fetch API in action. Your browser sends an HTTP request to the server, the server reads the JSON file, and the response comes back as data that JavaScript can render into HTML elements.

  • Create a new file called app.js inside your public folder.
  • Paste the following code into public/app.js:
// Get references to DOM elements
const bookmarkForm = document.getElementById('bookmark-form');
const nameInput = document.getElementById('name-input');
const urlInput = document.getElementById('url-input');
const bookmarkList = document.getElementById('bookmark-list');

// Fetch bookmarks from the API and render them on the page
async function loadBookmarks() {
  const response = await fetch('/api/bookmarks');
  const bookmarks = await response.json();

  // Clear existing list before re-rendering
  bookmarkList.innerHTML = '';

  bookmarks.forEach((bookmark) => {
    const li = document.createElement('li');
    const a = document.createElement('a');
    a.href = bookmark.url;
    a.textContent = bookmark.name;
    a.target = '_blank';
    li.appendChild(a);
    bookmarkList.appendChild(li);
  });
}

// Load bookmarks when the page first opens
loadBookmarks();

What does this code do?

  • The four const lines at the top grab references to the HTML elements by their id attributes. This lets JavaScript interact with the form, inputs, and list.
  • The loadBookmarks() function sends a GET request to /api/bookmarks, waits for the JSON response, then loops over the array to create a clickable link for each bookmark.
  • Setting bookmarkList.innerHTML = '' clears the list before re-rendering. This prevents duplicates when the function is called multiple times.
  • The final loadBookmarks() call at the bottom runs the function as soon as the page loads, so any existing bookmarks appear immediately.
  • Save public/app.js.
  • Refresh your browser at http://localhost:3000. If you added any bookmarks in Step 2 (via the console), you should see them displayed as blue clickable links below the form.

What if I don't see any bookmarks?

If you did not add bookmarks during Step 2, or if you reset your bookmarks.json back to an empty array [], the list will be empty. That is expected. You will add bookmarks through the form in the next step.

Bookmarks not showing up even though your JSON file has data?

  • Open your browser's DevTools console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and check for red error messages.
  • Make sure app.js is in the public folder and that your HTML has <script src="app.js"></script> before the closing </body> tag.
  • Verify your data/bookmarks.json contains valid JSON. Even a missing bracket will cause the API to fail.

Help me debug why my bookmarks are not rendering on the page.

✔️ Awesome, I've got everything!

Great. Double check you've saved all three files in your public/ folder.

ⓧ I'd like to double check the full code

Here is each file in full. Compare your code against these:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bookmark Saver</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>Bookmark Saver</h1>
    <form id="bookmark-form">
      <input type="text" id="name-input" placeholder="Bookmark name" required>
      <input type="url" id="url-input" placeholder="https://example.com" required>
      <button type="submit">Save Bookmark</button>
    </form>
    <ul id="bookmark-list"></ul>
  </div>
  <script src="app.js"></script>
</body>
</html>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

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

.container {
  max-width: 600px;
  margin: 0 auto;
  background: white;
  padding: 32px;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

h1 {
  margin-bottom: 24px;
  color: #333;
}

form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 32px;
}

input {
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-size: 16px;
}

input:focus {
  outline: none;
  border-color: #4a90d9;
}

button {
  padding: 12px;
  background-color: #4a90d9;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;
}

button:hover {
  background-color: #357abd;
}

ul {
  list-style: none;
}

li {
  padding: 12px;
  border-bottom: 1px solid #eee;
}

li:last-child {
  border-bottom: none;
}

li a {
  color: #4a90d9;
  text-decoration: none;
  font-size: 16px;
}

li a:hover {
  text-decoration: underline;
}
const bookmarkForm = document.getElementById('bookmark-form');
const nameInput = document.getElementById('name-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();

  bookmarkList.innerHTML = '';

  bookmarks.forEach((bookmark) => {
    const li = document.createElement('li');
    const a = document.createElement('a');
    a.href = bookmark.url;
    a.textContent = bookmark.name;
    a.target = '_blank';
    li.appendChild(a);
    bookmarkList.appendChild(li);
  });
}

loadBookmarks();

Your frontend can now display bookmarks from the server. Next up, you will connect the form so that typing in a new bookmark and clicking "Save Bookmark" actually sends it to your API.

Connect the Frontend to the API

Your frontend can already fetch and display bookmarks from the server. The missing piece is the other direction: sending new bookmarks FROM the form TO the API.

In this step, you'll wire up the form's submit button to send a POST request to your API. Once this connection is in place, the full client-server loop is complete.

In this step, get ready to:
  • Add a form submit event listener that sends POST requests to the API.
  • Test the complete flow and confirm bookmarks persist after a server restart.
Add the form submit event listener

Right now, clicking Save Bookmark does nothing useful. The browser tries to submit the form the old-fashioned way (a full page reload). You need to intercept that event and send a fetch request instead.

  • In app.js, find the loadBookmarks(); call at the very bottom of the file.
  • Add the following code directly above that line:
bookmarkForm.addEventListener('submit', async (e) => {
  // Stop the browser from refreshing the page
  e.preventDefault();

  // Grab the values from the form inputs
  const name = nameInput.value;
  const url = urlInput.value;

  // Send a POST request with the bookmark data as JSON
  await fetch('/api/bookmarks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, url })
  });

  // Clear the form inputs
  nameInput.value = '';
  urlInput.value = '';

  // Refresh the bookmark list to show the new entry
  loadBookmarks();
});

What does this code do?

e.preventDefault() stops the browser's default form behaviour (which would reload the page and lose your app state).

The fetch call sends the bookmark data to your POST route as JSON. The Content-Type header tells Express to parse the body with its built-in express.json() middleware.

After the server responds, the form inputs are cleared and loadBookmarks() re-fetches the full list so the new bookmark appears instantly.

  • Save app.js.

Not sure where to place the code?

The event listener goes between the closing } of the loadBookmarks function definition and the final loadBookmarks(); call. Make sure there is a blank line separating each section.

help me figure out where to place the event listener in app.js

Test the full flow

Your app is now fully wired up. Time to prove that data flows from the form, through the API, into the JSON file, and back to the page.

  • Make sure your server is running. If it is not, start it by running:
node server.js
  • Open your browser to http://localhost:3000.
  • Type NextWork in the Bookmark name field.
  • Type https://www.nextwork.org in the URL field.
  • Click Save Bookmark.

The form should clear and "NextWork" should appear as a clickable link in the list below.

  • Now test persistence. Go back to your terminal and press Ctrl+C to stop the server.
  • Restart it by running:
node server.js
  • Refresh your browser at http://localhost:3000.

Your "NextWork" bookmark should still be there. The server read it from bookmarks.json on startup, proving your data persists between restarts.

Why does the bookmark survive a restart?

When you clicked Save Bookmark, the POST route wrote the data to data/bookmarks.json on disk. When the server starts again, the GET route reads that same file. The JSON file acts as your database.

Bookmark not appearing after you click Save?

  • Open your browser's DevTools console (Cmd+Option+J on macOS, Ctrl+Shift+J on Windows). Look for red error messages. A 404 means the server is not running.
  • Check that you saved app.js after adding the event listener. Unsaved changes are not served to the browser.
  • Make sure the Content-Type header is spelled exactly as shown. Without it, Express cannot parse the request body and your bookmark will save as undefined.

help me debug why my bookmark form is not saving data

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js and that your bookmark persists after a server restart.

ⓧ I'd like to double check the full code

Here is the complete app.js file. Compare it with yours:

const bookmarkForm = document.getElementById('bookmark-form');
const nameInput = document.getElementById('name-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();

  bookmarkList.innerHTML = '';

  bookmarks.forEach((bookmark) => {
    const li = document.createElement('li');
    const a = document.createElement('a');
    a.href = bookmark.url;
    a.textContent = bookmark.name;
    a.target = '_blank';
    li.appendChild(a);
    bookmarkList.appendChild(li);
  });
}

bookmarkForm.addEventListener('submit', async (e) => {
  e.preventDefault();

  const name = nameInput.value;
  const url = urlInput.value;

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

  nameInput.value = '';
  urlInput.value = '';
  loadBookmarks();
});

loadBookmarks();

Your full stack app is alive. The frontend talks to the backend, the backend writes to disk, and everything survives a restart. Next up, a secret mission to add delete functionality and complete the core CRUD operations.

Secret mission

Add Delete Functionality

Your Bookmark Saver can create and read bookmarks. But what happens when you save a bad link or no longer need a bookmark? In this secret mission, you'll add a Delete button to each bookmark that removes it from the server permanently.

Clean Up Your Resources

Clean Up Your Resources

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

Resources you used:

  • Node.js/Express server running on localhost:3000.
  • Project folder with server.js, public/ directory, and data/ directory.
  • node_modules folder containing Express and its dependencies.
  • data/bookmarks.json file storing your saved bookmarks.

Keep everything running

No action needed. Choose this if you're still actively building or want to keep experimenting with your Bookmark Saver app.

  • Your server stays running as long as you keep the terminal open.
  • Your bookmarks remain saved in data/bookmarks.json.
  • You can access the app anytime at http://localhost:3000.

Pause - I'll come back to this later

Shut down the running server to free up your terminal. Your project files and saved bookmarks stay on disk for next time.

  • In the terminal where your server is running, press Ctrl+C to stop the server.
  • Your project folder and all files remain on your Desktop.
  • To resume later, open a terminal, navigate to your project folder, and run node server.js.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your code, saved bookmarks, and installed packages.

  • In the terminal where your server is running, press Ctrl+C to stop the server.
  • Delete the entire project folder from your Desktop.
  • Node.js itself stays installed on your machine for future projects.

That's a Wrap!

That's a Wrap!

Nice work! You just built a full stack web application from scratch.

You've learned how to:

  • Created an Express server with REST API routes that read and write data to a JSON file for persistence.
  • Built a frontend that communicates with your backend using the Fetch API to send HTTP requests and dynamically render data.
  • Implemented the client-server pattern: your frontend sends requests, your backend processes them and returns responses.
  • Secret Mission: Added DELETE functionality, completing the core CRUD operations (Create, Read, Delete).

Ready to quiz yourself?