Build a Bookmark Manager App

Build a full-stack bookmark manager with a React frontend and Node.js API.

Introduction

30 Second Summary

You save links all the time in browser bookmarks, notes apps, or messages to yourself. But what if you built your own bookmark manager that stores data on a server you control?

In this project, you will build a full-stack Bookmark Manager using React for the frontend and Node.js for the backend. You'll create, view, and delete bookmarks through a clean interface that talks to your own REST API.

What You'll Build

A full-stack Bookmark Manager application where a React frontend lets you save website bookmarks with a title and URL, view your collection, and delete entries you no longer need.

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

  • A React single-page app built with Vite that displays and manages your bookmarks.
  • A Node.js/Express REST API with GET, POST, and DELETE endpoints handling bookmark data.
  • Frontend-to-backend communication using the fetch API and CORS.
  • Secret Mission: Add an "edit bookmark" feature with inline editing.

Are there any prerequisites?

This project assumes a basic understanding of HTML, CSS, and JavaScript. You do not need prior experience with React or Node.js.

Set Up Your Development Environment

Your bookmark manager has two halves: a React frontend for the UI and a Node.js backend for the data. Before building features, you need the frontend project scaffolded and ready to go.

In this step, you'll use Vite to generate a React starter project inside your client folder. Vite creates all the files and configuration so you can jump straight into coding.

In this step, get ready to:
  • Scaffold the React frontend with Vite.
  • Install the project dependencies.
  • Prepare the app's entry file.
Scaffold the React app with Vite

Vite is a modern build tool that scaffolds React projects in seconds. It generates a ready-to-run starter app with hot reloading built in.

  • Navigate to your client folder by running this command:
cd ~/Desktop/bookmark-manager/client
  • Scaffold the React app into this folder by running:
npm create vite@latest . -- --template react

What does this command do?

  • npm create vite@latest downloads and runs the Vite project scaffolding tool.
  • . tells Vite to scaffold into the current folder instead of creating a new one.
  • -- --template react selects the React template so Vite skips asking you to choose.

npm will ask to install create-vite

You'll see a prompt saying "Need to install the following packages: create-vite@latest. Ok to proceed?" Type y and press Enter to continue.

  • Install the project dependencies by running:
npm install
  • You should see a message like "added X packages" in the terminal. This confirms all dependencies were installed into a node_modules folder.

Seeing an error during npm install?

  • Make sure you're inside the client folder, not the bookmark-manager root. Run pwd to check your current location.
  • If you see a network error, check your internet connection and try running npm install again.

Why is npm install failing in my Vite React project?

Prepare the entry file

The scaffold generated a main.jsx file that acts as your app's starting point. You'll update it to use a consistent import style that the rest of the project follows.

  • Open client/src/main.jsx in VS Code and replace its entire contents with this code:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

// Render the App component inside React's StrictMode wrapper
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

What does this code do?

  • It imports React and ReactDOM, the two libraries needed to render a React app in the browser.
  • ReactDOM.createRoot attaches your React app to the HTML element with id root.
  • React.StrictMode enables extra development warnings that help you catch mistakes early.
  • Save main.jsx.
  • Confirm the file saved by checking that the dot or indicator next to the filename tab in VS Code disappears.

Not sure which file to edit?

  • In VS Code's Explorer sidebar, expand client then src. You should see main.jsx listed there.
  • If you don't see a src folder, the Vite scaffold may not have run correctly. Go back and re-run the scaffold command from inside the client folder.

I can't find main.jsx in my project. Where should it be?

✔️ Awesome, I've got everything!

Great. Double check you've saved main.jsx and you're ready for the next step.

ⓧ I'd like to double check the full code

Here's what client/src/main.jsx should look like:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Your React frontend is scaffolded and ready. Next up, you'll build the Express API that stores and serves your bookmark data.

Build the Express API

Your React frontend needs somewhere to get its data from. Right now, the client folder is ready, but there is no server to talk to.

In this step, you will build a REST API using Express. The API stores bookmarks in memory and exposes endpoints for reading, creating, and deleting them.

In this step, get ready to:
  • Initialize the server project and install dependencies.
  • Create the Express server with three API endpoints.
  • Start the server and confirm it responds correctly.
Initialize the server project

Before writing any server code, you need a package.json file that tracks your project's dependencies and configuration.

  • Navigate into your server folder by running this command:
cd ~/Desktop/bookmark-manager/server
  • Initialize a new Node.js project by running:
npm init -y

What does this command do?

The -y flag tells npm to accept all default values without asking questions. This creates a package.json file that tracks your server's metadata and dependencies.

The generated package.json defaults to CommonJS modules. Your project uses ES Modules (the import syntax) for consistency with the React client.

  • Open server/package.json in VS Code.
  • Find this line near the top:
"main": "index.js",
  • Replace it with these two lines so the file uses ES Modules and points to your server file:
"type": "module",
"main": "server.js",
  • Save package.json.
  • Install Express and the CORS middleware by running:
npm install express cors

What are Express and CORS?

Express is a web framework for Node.js that makes it easy to create API endpoints. CORS (Cross-Origin Resource Sharing) is middleware that allows your React app on port 5173 to talk to your server on port 3001.

Without CORS, the browser blocks requests between different ports for security reasons.

  • Confirm the install succeeded by checking that a node_modules folder appeared inside server/.
ls

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

Seeing an error during install?

Make sure you are inside the server folder, not the root bookmark-manager folder. Run pwd to check your current location.

If you see permission errors, try closing and reopening your terminal, then navigate back to the server folder.

Help me troubleshoot my npm install error in the server folder.

Create the Express server

Now you will write the server code. The file handles incoming HTTP requests and sends back JSON responses.

  • Create a new file called server.js inside your server folder.
  • Add the server setup and sample data by pasting this code into server.js:
import express from 'express';
import cors from 'cors';

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

// Enable CORS so the React app can make requests
app.use(cors());
// Parse incoming JSON request bodies
app.use(express.json());

// Sample bookmarks stored in memory
let bookmarks = [
  { id: 1, title: 'React Docs', url: 'https://react.dev' },
  { id: 2, title: 'Node.js', url: 'https://nodejs.org' }
];
let nextId = 3;

// GET endpoint - return all bookmarks
app.get('/bookmarks', (req, res) => {
  res.json(bookmarks);
});

What does this code do?

The first few lines import Express and CORS, then create an app instance on port 3001.

app.use(cors()) allows cross-origin requests. app.use(express.json()) tells Express to automatically parse JSON data sent in request bodies.

The bookmarks array acts as a simple in-memory database. The GET endpoint at /bookmarks sends back the full array as JSON whenever someone requests it.

  • Add the POST endpoint below the GET endpoint. This handles creating new bookmarks:
// POST endpoint - add a new bookmark
app.post('/bookmarks', (req, res) => {
  const { title, url } = req.body;
  if (!title || !url) {
    return res.status(400).json({ error: 'Title and URL are required' });
  }
  const newBookmark = { id: nextId++, title, url };
  bookmarks.push(newBookmark);
  res.status(201).json(newBookmark);
});

What does this code do?

The POST endpoint extracts title and url from the request body. If either is missing, it returns a 400 error.

Otherwise, it creates a new bookmark object with a unique ID, pushes it into the array, and responds with status 201 (created) plus the new bookmark data.

  • Add the DELETE endpoint and the server start command below the POST endpoint:
// DELETE endpoint - remove a bookmark by ID
app.delete('/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();
});

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

What does this code do?

The DELETE endpoint reads the bookmark ID from the URL (:id is a route parameter). It finds that bookmark in the array and removes it with splice.

If the ID does not match any bookmark, it returns a 404 error. Status 204 means the deletion succeeded with no content to return.

app.listen starts the server and prints a message confirming it is running on port 3001.

  • Save server.js.

✔️ Awesome, I've got everything!

Great. Double check you have saved server.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete server.js file. Make sure yours matches exactly:

import express from 'express';
import cors from 'cors';

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

app.use(cors());
app.use(express.json());

let bookmarks = [
  { id: 1, title: 'React Docs', url: 'https://react.dev' },
  { id: 2, title: 'Node.js', url: 'https://nodejs.org' }
];
let nextId = 3;

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

app.post('/bookmarks', (req, res) => {
  const { title, url } = req.body;
  if (!title || !url) {
    return res.status(400).json({ error: 'Title and URL are required' });
  }
  const newBookmark = { id: nextId++, title, url };
  bookmarks.push(newBookmark);
  res.status(201).json(newBookmark);
});

app.delete('/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();
});

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

Seeing red squiggly lines in VS Code?

If VS Code shows import errors, make sure you added "type": "module" to your package.json. Without it, Node.js expects the older require() syntax instead of import.

Help me fix import errors in my Express server file.

Start the server and test the API

Your server code is ready. Time to run it and confirm the endpoints work before connecting the React frontend.

  • Make sure your terminal is inside the server folder, then start the server by running:
node server.js

You should see this message in your terminal:

Server running on http://localhost:3001

Why keep this terminal open?

The server runs as a long-lived process. It listens for requests until you stop it with Ctrl+C. Keep this terminal window open for the rest of the project.

  • Test the GET endpoint by opening your browser and navigating to:
http://localhost:3001/bookmarks

You should see a JSON array with two sample bookmarks displayed in the browser.

Don't see JSON in the browser?

If you see "This site can't be reached," your server is not running. Check the terminal for error messages. A common cause is a typo in server.js that prevents it from starting.

If you see an empty page, double check that the bookmarks array in your code contains the two sample entries.

Help me figure out why my Express server is not responding.

Your API is live and responding to requests. Next up, you will build the React frontend that talks to these endpoints and displays bookmarks in a proper UI.

Build the React Bookmark List

Your Express API is live and serving bookmark data. Now you need a frontend that can display those bookmarks and let you add new ones.

React components manage their own state and render UI based on that state. In this step, you'll build three components inside App.jsx that communicate with your API to fetch, display, and create bookmarks.

In this step, get ready to:
  • Build the main App component that fetches bookmarks on page load.
  • Create an AddBookmark form that sends new bookmarks to the API.
  • Add a BookmarkItem component that renders each saved bookmark.
Replace the default App.jsx with the App component

React uses useState to store data and useEffect to run code when a component first appears on screen. Your App component will use both hooks to fetch the bookmark list from your Express API as soon as the page loads.

  • Open client/src/App.jsx in VS Code.
  • Select all the default content by pressing Cmd+A (macOS) or Ctrl+A (Windows), then delete it.
  • Paste the following code to set up the imports, API URL, and the main App component:
import { useState, useEffect } from 'react';
import './App.css';

const API_URL = 'http://localhost:3001/bookmarks';

function App() {
  // Store the list of bookmarks in state
  const [bookmarks, setBookmarks] = useState([]);

  // Fetch bookmarks from the API when the component first mounts
  useEffect(() => {
    fetch(API_URL)
      .then(res => res.json())
      .then(data => setBookmarks(data));
  }, []);

  // Add a new bookmark to the local list
  const handleAdd = (newBookmark) => {
    setBookmarks([...bookmarks, newBookmark]);
  };

  // Remove a bookmark from the local list
  const handleDelete = (id) => {
    setBookmarks(bookmarks.filter(b => b.id !== id));
  };

  return (
    <div className="app">
      <h1>Bookmark Manager</h1>
      <AddBookmark onAdd={handleAdd} />
      <ul className="bookmark-list">
        {bookmarks.map(bookmark => (
          <BookmarkItem
            key={bookmark.id}
            bookmark={bookmark}
            onDelete={handleDelete}
          />
        ))}
      </ul>
    </div>
  );
}

export default App;

What does this code do?

  • API_URL stores the Express server address so all fetch calls use the same base URL.
  • useState([]) creates an empty array to hold your bookmarks. React re-renders the component whenever this array changes.
  • useEffect with an empty [] dependency array runs once when the component first mounts, fetching bookmarks from your Express API.
  • handleAdd and handleDelete update the local state so the UI stays in sync without needing another API call.
  • The JSX references AddBookmark and BookmarkItem. You'll define these components next.
  • Save App.jsx.
  • You won't see changes in the browser yet because the React dev server isn't running. The next chunk adds the form component.

Seeing red squiggles in VS Code?

VS Code may show warnings about AddBookmark and BookmarkItem being undefined. This is expected. You'll add these components in the next two chunks.

Why is VS Code showing errors about undefined components in my App.jsx?

Create the AddBookmark form component

The AddBookmark component renders a form with two inputs. When submitted, it sends a POST request to your Express API with the new bookmark's title and URL.

  • In App.jsx, find the line that says const API_URL = 'http://localhost:3001/bookmarks';.
  • Paste the following code below that line (above the App function):
function AddBookmark({ onAdd }) {
  // Track the form input values
  const [title, setTitle] = useState('');
  const [url, setUrl] = useState('');

  const handleSubmit = async (e) => {
    // Prevent the page from reloading on form submit
    e.preventDefault();
    if (!title || !url) return;

    // Send the new bookmark to the Express API
    const response = await fetch(API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title, url })
    });

    if (response.ok) {
      const newBookmark = await response.json();
      // Tell the parent component about the new bookmark
      onAdd(newBookmark);
      // Clear the form inputs
      setTitle('');
      setUrl('');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="add-form">
      <input
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <input
        type="url"
        placeholder="https://example.com"
        value={url}
        onChange={(e) => setUrl(e.target.value)}
      />
      <button type="submit">Add Bookmark</button>
    </form>
  );
}

What does this code do?

  • { onAdd } destructures the prop passed from App, giving this component a way to notify App when a bookmark is created.
  • handleSubmit sends a POST request with the title and URL as JSON. If the API responds successfully, it calls onAdd with the new bookmark and clears the form.
  • Each <input> is a controlled component. Its value is tied to state, so React always knows what the user typed.
  • Save App.jsx.
  • You won't see browser changes yet. The next chunk adds the final missing component.

Getting a syntax error on save?

Make sure you pasted the AddBookmark function between the API_URL line and the App function. It should not be inside another function.

Help me fix a syntax error in my AddBookmark component.

Add the BookmarkItem component to complete the file

Each bookmark in your list needs its own component to render the link and a delete button. BookmarkItem handles displaying a single bookmark and sending a DELETE request when the button is clicked.

  • In App.jsx, find the closing } of your AddBookmark function.
  • Paste the following code below it (above the App function):
function BookmarkItem({ bookmark, onDelete }) {
  const handleDelete = async () => {
    // Send a DELETE request to remove this bookmark from the API
    const response = await fetch(`${API_URL}/${bookmark.id}`, {
      method: 'DELETE'
    });
    if (response.ok) {
      // Tell the parent to remove this bookmark from state
      onDelete(bookmark.id);
    }
  };

  return (
    <li className="bookmark-item">
      <a href={bookmark.url} target="_blank" rel="noopener noreferrer">
        {bookmark.title}
      </a>
      <button onClick={handleDelete} className="delete-btn">Delete</button>
    </li>
  );
}

What does this code do?

  • BookmarkItem receives a single bookmark object and an onDelete callback as props.
  • handleDelete sends a DELETE request to the API using the bookmark's ID. On success, it calls onDelete so App removes the bookmark from state.
  • The <a> tag opens the bookmark URL in a new tab. rel="noopener noreferrer" is a security best practice for external links.
  • Save App.jsx.
  • All three components are now in place. The red squiggles in VS Code should disappear since AddBookmark and BookmarkItem are both defined.

Still seeing errors after saving?

Make sure BookmarkItem is placed between AddBookmark and App (not inside either function). Also check that you haven't accidentally deleted the import line at the top of the file.

Help me debug errors in my App.jsx bookmark components.

✔️ Awesome, I've got everything!

Great. Double check you've saved App.jsx and that VS Code shows no red error indicators in the file.

ⓧ I'd like to double check the full code

Here's the complete App.jsx file. Make sure yours matches exactly:

import { useState, useEffect } from 'react';
import './App.css';

const API_URL = 'http://localhost:3001/bookmarks';

function AddBookmark({ onAdd }) {
  const [title, setTitle] = useState('');
  const [url, setUrl] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!title || !url) return;

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

    if (response.ok) {
      const newBookmark = await response.json();
      onAdd(newBookmark);
      setTitle('');
      setUrl('');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="add-form">
      <input
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <input
        type="url"
        placeholder="https://example.com"
        value={url}
        onChange={(e) => setUrl(e.target.value)}
      />
      <button type="submit">Add Bookmark</button>
    </form>
  );
}

function BookmarkItem({ bookmark, onDelete }) {
  const handleDelete = async () => {
    const response = await fetch(`${API_URL}/${bookmark.id}`, {
      method: 'DELETE'
    });
    if (response.ok) {
      onDelete(bookmark.id);
    }
  };

  return (
    <li className="bookmark-item">
      <a href={bookmark.url} target="_blank" rel="noopener noreferrer">
        {bookmark.title}
      </a>
      <button onClick={handleDelete} className="delete-btn">Delete</button>
    </li>
  );
}

function App() {
  const [bookmarks, setBookmarks] = useState([]);

  useEffect(() => {
    fetch(API_URL)
      .then(res => res.json())
      .then(data => setBookmarks(data));
  }, []);

  const handleAdd = (newBookmark) => {
    setBookmarks([...bookmarks, newBookmark]);
  };

  const handleDelete = (id) => {
    setBookmarks(bookmarks.filter(b => b.id !== id));
  };

  return (
    <div className="app">
      <h1>Bookmark Manager</h1>
      <AddBookmark onAdd={handleAdd} />
      <ul className="bookmark-list">
        {bookmarks.map(bookmark => (
          <BookmarkItem
            key={bookmark.id}
            bookmark={bookmark}
            onDelete={handleDelete}
          />
        ))}
      </ul>
    </div>
  );
}

export default App;

Why build the frontend before starting the dev server?

Writing all three components first means you avoid a broken intermediate state in the browser. React would throw errors if the App component references AddBookmark or BookmarkItem before they exist.

You'll start the dev server and see everything working together in Step 5.

Your React components are wired up and ready to talk to the Express API. Next up, you'll add delete functionality styling and make the bookmark list look polished.

Add Delete Functionality and Style the App

In the previous step, you built three React components and connected them to your Express API. The AddBookmark form can send POST requests, and the BookmarkItem component is wired up with a delete button.

In this step, you'll walk through how the delete flow works from button click to API call to state update. Then you'll add CSS styling to transform the app from bare HTML into a clean, usable interface.

In this step, get ready to:
  • Walk through how the delete button communicates with the API.
  • Understand how React state keeps the UI in sync after deletion.
  • Style the app with CSS for a polished, usable look.
Understand how the delete button works

Your BookmarkItem component already has a delete button connected to the Express API. When a user clicks "Delete", a chain of events fires that removes the bookmark from both the server and the UI.

  • In App.jsx, find the handleDelete function inside BookmarkItem:
const handleDelete = async () => {
  const response = await fetch(`${API_URL}/${bookmark.id}`, {
    method: 'DELETE'
  });
  if (response.ok) {
    onDelete(bookmark.id);
  }
};

How does this delete flow work?

When the learner clicks the Delete button, handleDelete fires and sends a DELETE request to /bookmarks/:id on your Express server.

The server finds that bookmark in its array, removes it with splice, and responds with a 204 status ("success, no content"). Back in the browser, response.ok confirms the deletion succeeded, so the component calls onDelete(bookmark.id) to notify the parent.

See how state updates after deletion

Deleting from the server is only half the job. The UI also needs to update instantly so the deleted bookmark disappears without a page refresh.

  • In App.jsx, find the handleDelete function inside the App component:
const handleDelete = (id) => {
  setBookmarks(bookmarks.filter(b => b.id !== id));
};

What does this state update do?

The filter method creates a new array containing every bookmark EXCEPT the one with the matching id. Calling setBookmarks with this new array triggers a re-render.

React sees the state changed and automatically re-renders the list without the deleted item. This is the core pattern for keeping your UI in sync with server data.

Style the bookmark manager

The bookmark manager works, but it looks like unstyled HTML. CSS will give the app a clean layout with proper spacing, colors, and interactive hover states.

  • Open App.css inside client/src/ in VS Code.
  • Select all the existing content and delete it.
  • Add the app container and form styles by pasting this code:
/* Center the app and set a readable max width */
.app {
  max-width: 600px;
  margin: 2rem auto;
  padding: 1rem;
  font-family: system-ui, -apple-system, sans-serif;
}

h1 {
  text-align: center;
  color: #333;
}

/* Lay out the add-bookmark form in a horizontal row */
.add-form {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.add-form input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.9rem;
}

.add-form button {
  padding: 0.5rem 1rem;
  background: #4f46e5;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.add-form button:hover {
  background: #4338ca;
}

What do these styles do?

  • .app centers the content with a max width of 600px so it doesn't stretch across wide screens.
  • .add-form uses flexbox to lay out the title input, URL input, and button side by side.
  • The :hover rule darkens the button background when a user moves their cursor over it.
  • Below the form styles you just added, paste the bookmark list and delete button styles:
/* Remove default list bullets and padding */
.bookmark-list {
  list-style: none;
  padding: 0;
}

/* Each bookmark is a flex row with space between link and button */
.bookmark-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  margin-bottom: 0.5rem;
}

.bookmark-item a {
  color: #4f46e5;
  text-decoration: none;
}

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

/* Red delete button, smaller than the add button */
.delete-btn {
  padding: 0.25rem 0.5rem;
  background: #ef4444;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
}

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

What do these styles do?

  • .bookmark-list removes the default bullet points and left padding that browsers add to <ul> elements.
  • .bookmark-item uses justify-content: space-between to push the link to the left and the delete button to the right.
  • .delete-btn uses a red background to signal a destructive action, making it visually distinct from the blue "Add" button.
  • Save App.css.

You won't see the styled result yet because the React dev server isn't running. You'll start it in the next step and see everything come together.

Not sure if your CSS is correct?

Check that you have both chunks pasted in order with no duplicate selectors. The file should start with .app { and end with .delete-btn:hover { followed by its closing brace.

help me check my App.css file is correct

✔️ Awesome, I've got everything!

Great. Make sure you've saved App.css before moving on.

ⓧ I'd like to double check the full code

.app {
  max-width: 600px;
  margin: 2rem auto;
  padding: 1rem;
  font-family: system-ui, -apple-system, sans-serif;
}

h1 {
  text-align: center;
  color: #333;
}

.add-form {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.add-form input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 0.9rem;
}

.add-form button {
  padding: 0.5rem 1rem;
  background: #4f46e5;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.add-form button:hover {
  background: #4338ca;
}

.bookmark-list {
  list-style: none;
  padding: 0;
}

.bookmark-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  margin-bottom: 0.5rem;
}

.bookmark-item a {
  color: #4f46e5;
  text-decoration: none;
}

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

.delete-btn {
  padding: 0.25rem 0.5rem;
  background: #ef4444;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
}

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

Your app is fully coded and styled. Next up, you'll fire up both servers and test the complete flow of adding, viewing, and deleting bookmarks.

Run and Test the Full Stack

Your React frontend is complete with components for displaying, adding, and deleting bookmarks. The Express API has been handling data operations behind the scenes.

Now it's time to see everything work together. In this step, you'll run both applications side by side and test the full create-read-delete flow in your browser.

In this step, get ready to:
  • Confirm the Express server is running in its own terminal.
  • Start the React dev server in a second terminal.
  • Test adding, viewing, and deleting bookmarks in the browser.
Confirm the Express server is running

Your full-stack app needs two processes running at the same time: the Express API (serving data) and the React dev server (serving the UI). The Express server should already be running from Step 2.

  • Check the terminal window where you started your Express server. You should see Server running on http://localhost:3001 in the output.

✔️ I see the server running message

Your Express server is ready. Keep that terminal window open and move on to the next substep.

ⓧ I don't see the message or the terminal is closed

  • Open a terminal in VS Code by pressing Ctrl+` (macOS and Windows).
  • Navigate to the server folder and start the server by running these commands:
cd ~/Desktop/bookmark-manager/server
node server.js

You should see Server running on http://localhost:3001 printed in the terminal.

Start the React dev server

The React dev server compiles your frontend code and serves it to the browser. It runs on a different port from Express so both can operate simultaneously.

  • Open a second terminal in VS Code by clicking the + icon in the terminal panel (top right of the terminal area).

Why a second terminal?

The Express server occupies the first terminal because it runs continuously. You need a separate terminal for the React dev server since it also runs continuously. Both must stay active for the full-stack app to work.

  • Navigate to the client folder and start the React dev server by running these commands:
cd ~/Desktop/bookmark-manager/client
npm run dev

What does this command do?

The npm run dev command starts Vite's development server. It compiles your React code and serves it at http://localhost:5173 with hot module replacement, so changes you save appear instantly in the browser.

  • You should see output in your terminal showing the Vite dev server is running, along with a local URL.
  • Open Google Chrome.
  • Navigate to http://localhost:5173 in the address bar.

You should see the Bookmark Manager heading, the add form with two input fields, and two default bookmarks: React Docs and Node.js.

Seeing a blank page or errors?

  • Check that both terminals are still running. If the Express server terminal shows an error or has stopped, restart it with node server.js from the server folder.
  • Open the browser's developer console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows). Look for network errors that mention localhost:3001. These mean the Express server is not reachable.
  • Confirm the API_URL in App.jsx is set to http://localhost:3001/bookmarks.

Still stuck? Help me debug why my Bookmark Manager shows a blank page or doesn't load bookmarks from the API.

Test the full flow

With both servers running, you can now test every feature of your full-stack app. Each action you take in the browser triggers an HTTP request to the Express API.

  • Test adding a bookmark: type MDN Web Docs in the title field and https://developer.mozilla.org in the URL field.
  • Click Add Bookmark.

The new bookmark should appear at the bottom of the list immediately. The form fields clear, ready for another entry.

  • Test deleting a bookmark: click the red Delete button next to any bookmark.

The bookmark disappears from the list without a page reload. React updates the UI state locally after the API confirms the deletion.

  • Test data persistence: refresh the page by pressing Cmd+R (macOS) or Ctrl+R (Windows).

Your bookmarks should still be there. Any bookmarks you added remain, and any you deleted stay gone.

Why does data persist across refreshes?

The Express server stores bookmarks in a JavaScript array that lives in memory. As long as the server process stays running, that array keeps its data. Refreshing the browser just re-fetches from the server.

If you stop and restart the Express server, the data resets to the two default bookmarks. A production app would use a database to persist data permanently.

  • Test the bookmark links: click on a bookmark title. It should open the URL in a new browser tab.

Bookmarks not persisting after refresh?

  • Make sure you did not restart the Express server between adding bookmarks and refreshing. Restarting the server resets the in-memory data.
  • If bookmarks show briefly then vanish, check that the useEffect in your App component has an empty dependency array [] so it only fetches once on mount.

Need help? My bookmarks disappear after refreshing the page even though the Express server is still running.

Secret mission

Add Edit Functionality

Your Bookmark Manager can add and delete bookmarks, but what about fixing a typo in a title or updating a URL? In this secret mission, you'll add inline editing so users can update bookmarks without deleting and re-creating them.

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:

  • The bookmark-manager project folder on your Desktop (contains client and server subfolders).
  • Running Express server process (port 3001).
  • Running React dev server process (port 5173).
  • Node.js v22 LTS (installed system-wide).

Keep everything running

No action needed. Choose this if you're still actively building or plan to extend the project further.

  • Your Express server continues running on port 3001.
  • Your React dev server continues running on port 5173.
  • All project files remain in the bookmark-manager folder on your Desktop.
  • Node.js remains installed and available for other projects.

Pause - I'll come back to this later

Stop the running processes to free up terminal windows and ports. Your project files stay on disk so you can restart later.

  • In the terminal window running the Express server, press Ctrl+C to stop it.
  • In the terminal window running the React dev server, press Ctrl+C to stop it.
  • To restart later, open two terminal windows. Run node server.js from the server folder, and npm run dev from the client folder.

Delete - I don't want to use this again

Remove all project files and stop all processes. This cannot be undone.

  • In the terminal window running the Express server, press Ctrl+C to stop it.
  • In the terminal window running the React dev server, press Ctrl+C to stop it.
  • Delete the project folder by running this command:

macOS

rm -rf ~/Desktop/bookmark-manager

Windows

rmdir /s /q %USERPROFILE%\Desktop\bookmark-manager
  • To uninstall Node.js on macOS, run brew uninstall node if you installed it with Homebrew. If you used the installer, delete /usr/local/bin/node and /usr/local/lib/node_modules.
  • To uninstall Node.js on Windows, open Settings, select Apps, find Node.js in the list, and click Uninstall.

That's a Wrap!

That's a Wrap!

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

You've learned how to:

  • Built a React frontend with Vite that manages UI state with useState and useEffect hooks.
  • Created a REST API with Express that handles GET, POST, and DELETE operations.
  • Connected the frontend and backend with the fetch API and CORS, learning the client-server pattern that powers every production web app.
  • Added inline editing with a PUT endpoint and conditional rendering.

Ready to quiz yourself?